این صفحه در نظر گرفته شده است تا راهنمای توسعه دهندگان باشد تا اصول کلی را که شورای API در بررسی های API اعمال می کند، درک کنند.
علاوه بر پیروی از این دستورالعمل ها هنگام نوشتن API، توسعه دهندگان باید ابزار API Lint را اجرا کنند، که بسیاری از این قوانین را در بررسی هایی که علیه API ها اجرا می شود، رمزگذاری می کند.
این را به عنوان راهنمای قوانینی که توسط آن ابزار Lint رعایت میشود، بهعلاوه توصیههای کلی در مورد قوانینی که نمیتوان با دقت بالا در آن ابزار مدون کرد، در نظر بگیرید.
ابزار API Lint
API Lint در ابزار تجزیه و تحلیل استاتیک Metalava یکپارچه شده است و در حین اعتبارسنجی در CI به طور خودکار اجرا می شود. می توانید آن را به صورت دستی از یک پرداخت پلتفرم محلی با استفاده از m checkapi
یا یک پرداخت محلی AndroidX با استفاده از ./gradlew :path:to:project:checkApi
اجرا کنید.
قوانین API
پلتفرم Android و بسیاری از کتابخانههای Jetpack قبل از ایجاد این مجموعه دستورالعملها وجود داشتهاند، و سیاستهایی که بعداً در این صفحه ارائه میشوند به طور مداوم در حال تغییر هستند تا نیازهای اکوسیستم اندروید را برآورده کنند.
در نتیجه، برخی از API های موجود ممکن است از دستورالعمل ها پیروی نکنند. در موارد دیگر، ممکن است تجربه کاربری بهتری را برای توسعه دهندگان برنامه فراهم کند اگر یک API جدید با APIهای موجود سازگار بماند به جای اینکه به طور دقیق از دستورالعمل ها پیروی کند.
اگر سؤالات دشواری در مورد یک API وجود دارد که باید حل شود یا دستورالعمل هایی که باید به روز شوند، از قضاوت خود استفاده کنید و با شورای API تماس بگیرید.
اصول API
این دسته به جنبه های اصلی یک API اندروید مربوط می شود.
همه API ها باید پیاده سازی شوند
صرف نظر از مخاطبان یک API (به عنوان مثال، عمومی یا @SystemApi
)، همه سطوح API باید در هنگام ادغام یا نمایش به عنوان API اجرا شوند. موارد خرد API را با پیاده سازی ادغام نکنید تا در تاریخ بعدی ارائه شود.
سطوح API بدون پیاده سازی مشکلات متعددی دارند:
- هیچ تضمینی وجود ندارد که یک سطح مناسب یا کامل در معرض دید قرار گرفته باشد. تا زمانی که یک API توسط کلاینتها آزمایش یا استفاده نشود، هیچ راهی برای تأیید اینکه یک کلاینت دارای APIهای مناسب برای استفاده از این ویژگی است، وجود ندارد.
- API های بدون پیاده سازی را نمی توان در پیش نمایش های برنامه نویس آزمایش کرد.
- API های بدون پیاده سازی را نمی توان در CTS آزمایش کرد.
همه API ها باید تست شوند
این مطابق با الزامات پلتفرم CTS، سیاست های AndroidX و به طور کلی این ایده است که API ها باید پیاده سازی شوند.
آزمایش سطوح API تضمینی پایه برای قابل استفاده بودن سطح API ارائه می دهد و ما موارد استفاده مورد انتظار را بررسی کرده ایم. آزمایش وجود کافی نیست. رفتار خود API باید آزمایش شود.
تغییری که یک API جدید اضافه میکند باید شامل تستهای مربوطه در همان مبحث CL یا Gerrit باشد.
APIها نیز باید قابل آزمایش باشند. شما باید بتوانید به این سوال پاسخ دهید که "یک توسعه دهنده برنامه چگونه کدی را که از API شما استفاده می کند تست می کند؟"
همه APIها باید مستند باشند
مستندسازی بخش کلیدی قابلیت استفاده API است. در حالی که نحو یک سطح API ممکن است واضح به نظر برسد، هر کلاینت جدیدی معنای، رفتار یا زمینه پشت API را درک نخواهد کرد.
همه APIهای تولید شده باید با دستورالعمل ها مطابقت داشته باشند
APIهای تولید شده توسط ابزارها باید از دستورالعملهای API مشابه کدهای دستنویس پیروی کنند.
ابزارهایی که برای تولید API ها منع می شوند:
-
AutoValue
: دستورالعملها را به روشهای مختلف نقض میکند، به عنوان مثال، هیچ راهی برای پیادهسازی کلاسهای ارزش نهایی یا سازندههای نهایی با روش کارکرد AutoValue وجود ندارد.
سبک کد
این دسته به سبک کد کلی مربوط می شود که توسعه دهندگان باید از آن استفاده کنند، به خصوص هنگام نوشتن API های عمومی.
به جز موارد ذکر شده، از قراردادهای استاندارد کدگذاری پیروی کنید
قراردادهای کدنویسی اندروید برای مشارکت کنندگان خارجی در اینجا مستند شده است:
https://source.android.com/source/code-style.html
به طور کلی، ما تمایل داریم از قوانین کدگذاری استاندارد جاوا و کاتلین پیروی کنیم.
حروف اختصاری نباید در نام روش ها با حروف بزرگ نوشته شوند
به عنوان مثال: نام متد باید runCtsTests
باشد نه runCTSTests
.
نام ها نباید به Impl ختم شوند
این جزئیات پیاده سازی را آشکار می کند، از آن اجتناب کنید.
کلاس ها
این بخش قوانین مربوط به کلاس ها، رابط ها و ارث را توضیح می دهد.
کلاس های عمومی جدید را از کلاس پایه مناسب به ارث ببرید
وراثت عناصر API را در زیر کلاس شما نشان می دهد که ممکن است مناسب نباشند. برای مثال، یک زیر کلاس عمومی جدید از FrameLayout
شبیه FrameLayout
به علاوه رفتارها و عناصر API جدید است. اگر آن API ارثی شده برای مورد استفاده شما مناسب نیست، از یک کلاس بالاتر از درخت، به عنوان مثال، ViewGroup
یا View
، ارث بری کنید.
اگر وسوسه شدید که روشهای کلاس پایه را برای پرتاب UnsupportedOperationException
نادیده بگیرید، در مورد کلاس پایه که استفاده میکنید تجدید نظر کنید.
از کلاس های مجموعه پایه استفاده کنید
چه یک مجموعه را به عنوان آرگومان در نظر بگیرید یا آن را به عنوان یک مقدار برگردانید، همیشه کلاس پایه را بر پیاده سازی خاص ترجیح دهید (مانند بازگشت List<Foo>
به جای ArrayList<Foo>
).
از یک کلاس پایه استفاده کنید که محدودیت های مناسب برای API را بیان می کند. به عنوان مثال، از List
برای یک API که مجموعه آن باید سفارش داده شود، و از Set
برای یک API که مجموعه آن باید از عناصر منحصر به فرد تشکیل شده باشد، استفاده کنید.
در کاتلین، مجموعه های تغییرناپذیر را ترجیح دهید. برای جزئیات بیشتر به تغییرپذیری مجموعه مراجعه کنید.
کلاس های انتزاعی در مقابل رابط ها
جاوا 8 از روشهای رابط پیشفرض پشتیبانی میکند، که به طراحان API اجازه میدهد تا با حفظ سازگاری باینری، متدهایی را به رابطها اضافه کنند. کد پلتفرم و تمام کتابخانه های Jetpack باید جاوا 8 یا جدیدتر را هدف قرار دهند.
در مواردی که اجرای پیشفرض بدون حالت است، طراحان API باید اینترفیسها را به کلاسهای انتزاعی ترجیح دهند - یعنی روشهای رابط پیشفرض را میتوان به عنوان فراخوانی برای روشهای رابط دیگر پیادهسازی کرد.
در مواردی که توسط اجرای پیش فرض نیاز به سازنده یا حالت داخلی است، باید از کلاس های انتزاعی استفاده شود.
در هر دو مورد، طراحان API می توانند برای ساده کردن استفاده به عنوان لامبدا، یک روش را انتزاعی بگذارند:
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
است
به عنوان مثال، اگر راهنمایی ابزار backporting مستلزم تداوم وضعیت مرتبط با View
و فراخوانی چندین روش در View
برای نصب backport باشد، TooltipHelper
یک نام کلاس قابل قبول خواهد بود.
کدهای تولید شده توسط IDL را مستقیماً به عنوان APIهای عمومی در معرض نمایش قرار ندهید
کد تولید شده توسط IDL را به عنوان جزئیات پیاده سازی نگه دارید. این شامل پروتوباف، سوکت ها، FlatBuffers یا هر سطح API غیر جاوا و غیر NDK می شود. با این حال، بیشتر IDL در اندروید در AIDL است، بنابراین این صفحه بر روی AIDL تمرکز دارد.
کلاس های AIDL تولید شده الزامات راهنمای سبک API را برآورده نمی کنند (برای مثال، نمی توانند از بارگذاری اضافه استفاده کنند) و ابزار AIDL به صراحت برای حفظ سازگاری API زبان طراحی نشده است، بنابراین نمی توانید آنها را در یک API عمومی جاسازی کنید.
در عوض، یک لایه API عمومی در بالای رابط AIDL اضافه کنید، حتی اگر در ابتدا یک پوشش کم عمق باشد.
رابط های بایندر
اگر رابط Binder
یک جزئیات پیادهسازی باشد، میتوان آن را آزادانه در آینده تغییر داد، با لایه عمومی که اجازه میدهد سازگاری مورد نیاز به عقب حفظ شود. برای مثال، ممکن است لازم باشد آرگومانهای جدیدی را به تماسهای داخلی اضافه کنید، یا ترافیک IPC را با استفاده از دستهبندی یا استریم، استفاده از حافظه مشترک یا موارد مشابه بهینه کنید. اگر رابط AIDL شما API عمومی نیز باشد، هیچ یک از اینها قابل انجام نیست.
برای مثال، FooService
بهعنوان یک API عمومی مستقیماً در معرض نمایش قرار ندهید:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
در عوض، رابط Binder
را در یک مدیر یا کلاس دیگر قرار دهید:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
اگر بعداً یک آرگومان جدید برای این فراخوانی مورد نیاز باشد، رابط داخلی می تواند اضافه بارهای حداقلی و راحت به API عمومی اضافه شود. میتوانید از لایه بستهبندی برای رسیدگی به سایر نگرانیهای مربوط به سازگاری با عقب با پیشرفت پیادهسازی استفاده کنید:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo, int flags);
}
public IFooManager {
public void doFoo(String foo) {
if (mAppTargetSdkLevel < 26) {
useOldFooLogic(); // Apps targeting API before 26 are broken otherwise
mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK);
} else {
mFooService.doFoo(foo, 0);
}
}
public void doFoo(String foo, int flags) {
mFooService.doFoo(foo, flags);
}
}
برای رابطهای Binder
که بخشی از پلتفرم اندروید نیستند (به عنوان مثال، یک رابط خدماتی که توسط سرویسهای Google Play برای استفاده برنامهها صادر میشود)، نیاز به یک رابط IPC پایدار، منتشر شده و نسخهشده به این معنی است که تکامل خود رابط بسیار سختتر است. با این حال، داشتن یک لایه پوششی در اطراف آن، برای مطابقت با سایر دستورالعملهای API و آسانتر کردن استفاده از همان API عمومی برای نسخه جدید رابط IPC، در صورت لزوم، ارزشمند است.
از اشیاء بایندر خام در API عمومی استفاده نکنید
یک شی Binder
به خودی خود معنایی ندارد و بنابراین نباید در API عمومی استفاده شود. یکی از موارد استفاده رایج استفاده از Binder
یا IBinder
به عنوان نشانه است زیرا دارای معنایی هویت است. به جای استفاده از یک شی Binder
خام، از یک کلاس توکن wrapper استفاده کنید.
public final class IdentifiableObject {
public Binder getToken() {...}
}
public final class IdentifiableObjectToken {
/**
* @hide
*/
public Binder getRawValue() {...}
/**
* @hide
*/
public static IdentifiableObjectToken wrapToken(Binder rawValue) {...}
}
public final class IdentifiableObject {
public IdentifiableObjectToken getToken() {...}
}
کلاس های مدیر باید نهایی باشد
کلاس های مدیر باید به عنوان final
اعلام شود. کلاس های مدیر با سرویس های سیستم صحبت می کنند و تنها نقطه تعامل هستند. نیازی به سفارشی سازی نیست پس آن را final
اعلام کنید.
از CompletableFuture یا Future استفاده نکنید
java.util.concurrent.CompletableFuture
دارای یک سطح API بزرگ است که امکان جهش دلخواه مقدار آینده را فراهم می کند و دارای پیش فرض های مستعد خطا است.
برعکس، java.util.concurrent.Future
از گوش دادن غیرانسدادی استفاده نمی کند و استفاده از آن را با کدهای ناهمزمان سخت می کند.
در کد پلتفرم و APIهای کتابخانه سطح پایین که توسط کاتلین و جاوا مصرف میشوند ، ترکیبی از یک فراخوان تکمیل، Executor
و اگر API از CancellationSignal
لغو پشتیبانی میکند، ترجیح میدهند.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
اگر Kotlin را هدف قرار می دهید ، توابع suspend
ترجیح دهید.
suspend fun asyncLoadFoo(): Foo
در کتابخانه های یکپارچه سازی خاص جاوا ، می توانید از ListenableFuture
Guava استفاده کنید.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
از اختیاری استفاده نکنید
در حالی که Optional
می تواند در برخی از سطوح API مزایایی داشته باشد، با سطح سطح API موجود Android سازگار نیست. @Nullable
و @NonNull
کمک ابزاری را برای ایمنی null
ارائه میکنند و Kotlin قراردادهای پوچپذیری را در سطح کامپایلر اجرا میکند و Optional
را غیرضروری میکند.
برای ابتدایی های اختیاری، از متدهای has
جفت و get
استفاده کنید. اگر مقدار تنظیم نشده باشد ( false
has
)، متد get
باید یک IllegalStateException
پرتاب کند.
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
از سازنده های خصوصی برای کلاس های غیرقابل استفاده استفاده کنید
کلاسهایی که فقط میتوانند توسط Builder
s ایجاد شوند، کلاسهایی که فقط شامل ثابتها یا متدهای استاتیک هستند، یا کلاسهای غیرقابل مثال باید حداقل شامل یک سازنده خصوصی باشند تا از نمونهسازی با استفاده از سازنده پیشفرض بدون آرگ جلوگیری شود.
public final class Log {
// Not instantiable.
private Log() {}
}
تک تن ها
Singleton دلسرد می شود زیرا آنها دارای معایب مربوط به آزمایش زیر هستند:
- ساخت و ساز توسط کلاس مدیریت می شود و از استفاده تقلبی جلوگیری می کند
- به دلیل ماهیت ساکن تک قلو، آزمایش ها نمی توانند هرمتیک باشند
- برای حل این مشکلات، توسعهدهندگان یا باید جزئیات داخلی تکتنه را بدانند یا یک پوشش در اطراف آن ایجاد کنند
الگوی تک نمونه را ترجیح دهید، که برای رسیدگی به این مسائل به یک کلاس پایه انتزاعی متکی است.
نمونه واحد
کلاسهای نمونه منفرد از یک کلاس پایه انتزاعی با سازنده 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
}
}
}
تفاوت Single Instance با singleton در این است که توسعه دهندگان می توانند نسخه جعلی SingleInstance
را ایجاد کنند و از چارچوب تزریق وابستگی خود برای مدیریت پیاده سازی بدون نیاز به ایجاد wrapper استفاده کنند، یا کتابخانه می تواند تقلبی خود را در یک مصنوع -testing
ارائه دهد.
کلاس هایی که منابع را آزاد می کنند باید AutoCloseable را پیاده سازی کنند
کلاسهایی که منابع را از طریق close
، release
، destroy
یا روشهای مشابه آزاد میکنند باید java.lang.AutoCloseable
پیادهسازی کنند تا به توسعهدهندگان این امکان را بدهد که به طور خودکار این منابع را هنگام استفاده از بلوک try-with-resources
پاک کنند.
از معرفی زیر کلاسهای View جدید در اندروید خودداری کنید.*
کلاسهای جدیدی را معرفی نکنید که بهطور مستقیم یا غیرمستقیم از android.view.View
در API عمومی پلتفرم (یعنی در android.*
) به ارث میبرند.
جعبه ابزار UI اندروید اکنون اولین نوشتن است. ویژگیهای جدید رابط کاربری که توسط این پلتفرم در معرض دید قرار میگیرند باید بهعنوان APIهای سطح پایینتر که میتوانند برای پیادهسازی Jetpack Compose و اجزای رابط کاربری اختیاری مبتنی بر View برای توسعهدهندگان در کتابخانههای Jetpack مورد استفاده قرار گیرند. ارائه این مؤلفهها در کتابخانهها فرصتهایی را برای پیادهسازی پسپورتشده زمانی که ویژگیهای پلتفرم در دسترس نیستند، فراهم میکند.
فیلدها
این قوانین در مورد فیلدهای عمومی در کلاس ها هستند.
زمینه های خام را در معرض دید قرار ندهید
کلاس های جاوا نباید فیلدها را مستقیماً در معرض دید قرار دهند. فیلدها باید خصوصی باشند و فقط با استفاده از گیرندهها و تنظیمکنندههای عمومی بدون در نظر گرفتن نهایی بودن یا نبودن این فیلدها قابل دسترسی باشند.
استثناهای نادر شامل ساختارهای داده پایه است که در آن نیازی به بهبود رفتار تعیین یا بازیابی یک فیلد نیست. در چنین مواردی، فیلدها باید با استفاده از قراردادهای نامگذاری متغیرهای استاندارد، به عنوان مثال، Point.x
و Point.y
نامگذاری شوند.
کلاس های کاتلین می توانند ویژگی ها را آشکار کنند.
فیلدهای در معرض دید باید نهایی شوند
فیلدهای خام به شدت منع می شوند (@ به عدم افشای فیلدهای خام مراجعه کنید ). اما در شرایط نادری که یک میدان به عنوان یک میدان عمومی در معرض دید قرار میگیرد، آن فیلد را final
کنید.
زمینه های داخلی نباید در معرض دید قرار گیرند
به نام فیلدهای داخلی در API عمومی ارجاع ندهید.
public int mFlags;
از عمومی به جای محافظت شده استفاده کنید
@ به استفاده از عمومی به جای محافظت شده مراجعه کنید
ثابت ها
اینها قوانینی در مورد ثابت های عمومی هستند.
ثابت های پرچم نباید با مقادیر int یا long همپوشانی داشته باشند
Flags به بیت هایی اشاره دارد که می توانند در مقداری اتحادیه ترکیب شوند. اگر اینطور نیست، متغیر یا 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
از پیشوندهای استاندارد برای ثابت ها استفاده کنید
بسیاری از ثابت های استفاده شده در اندروید برای چیزهای استاندارد مانند پرچم ها، کلیدها و اکشن ها هستند. این ثابت ها باید دارای پیشوندهای استاندارد باشند تا به عنوان این چیزها بیشتر قابل شناسایی باشند.
به عنوان مثال، موارد اضافی قصد باید با EXTRA_
شروع شود. اقدامات هدف باید با ACTION_
شروع شود. ثابت های مورد استفاده با Context.bindService()
باید با BIND_
شروع شوند.
نامها و دامنههای ثابت کلیدی
مقادیر ثابت رشته باید با نام ثابت مطابقت داشته باشد و عموماً باید در محدوده بسته یا دامنه باشد. به عنوان مثال:
public static final String FOO_THING = "foo"
نه به طور مداوم نامگذاری شده است و نه به طور مناسب محدوده. در عوض، در نظر بگیرید:
public static final String FOO_THING = "android.fooservice.FOO_THING"
پیشوندهای android
در ثابت های رشته محدوده برای پروژه منبع باز Android رزرو شده است.
کنشها و موارد اضافی، و همچنین ورودیهای Bundle، باید با استفاده از نام بستهای که در داخل آن تعریف شدهاند، فضای نامی داشته باشند.
package android.foo.bar {
public static final String ACTION_BAZ = "android.foo.bar.action.BAZ"
public static final String EXTRA_BAZ = "android.foo.bar.extra.BAZ"
}
از عمومی به جای محافظت شده استفاده کنید
@ به استفاده از عمومی به جای محافظت شده مراجعه کنید
از پیشوندهای ثابت استفاده کنید
ثابت های مرتبط باید همه با یک پیشوند شروع شوند. به عنوان مثال، برای مجموعه ای از ثابت ها برای استفاده با مقادیر پرچم:
public static final int SOME_VALUE = 0x01;
public static final int SOME_OTHER_VALUE = 0x10;
public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;
public static final int FLAG_SOME_OTHER_VALUE = 0x10;
public static final int FLAG_SOME_THIRD_VALUE = 0x100;
@ به استفاده از پیشوندهای استاندارد برای ثابت ها مراجعه کنید
از نام منابع ثابت استفاده کنید
شناسهها، ویژگیها و مقادیر عمومی باید با استفاده از قرارداد نامگذاری camelCase نامگذاری شوند، برای مثال @id/accessibilityActionPageUp
یا @attr/textAppearance
، مشابه فیلدهای عمومی در جاوا.
در برخی موارد، یک شناسه یا ویژگی عمومی شامل یک پیشوند مشترک است که با زیرخط جدا شده است:
- مقادیر پیکربندی پلتفرم مانند
@string/config_recentsComponentName
در config.xml - ویژگی های نمای خاص چیدمان مانند
@attr/layout_marginStart
در attrs.xml
تم ها و سبک های عمومی باید از قرارداد نامگذاری سلسله مراتبی PascalCase پیروی کنند، برای مثال @style/Theme.Material.Light.DarkActionBar
یا @style/Widget.Material.SearchView.ActionBar
، مشابه کلاس های تودرتو در جاوا.
طرحبندی و منابع قابل ترسیم نباید بهعنوان APIهای عمومی در معرض دید قرار گیرند. با این حال، اگر باید در معرض دید قرار گیرند، طرحبندیها و نقشههای عمومی باید با استفاده از قرارداد نامگذاری under_score نامگذاری شوند، برای مثال layout/simple_list_item_1.xml
یا drawable/title_bar_tall.xml
.
هنگامی که ثابت ها می توانند تغییر کنند، آنها را پویا کنید
کامپایلر ممکن است مقادیر ثابت را درون خطی قرار دهد، بنابراین حفظ مقادیر یکسان بخشی از قرارداد API در نظر گرفته می شود. اگر مقدار یک ثابت MIN_FOO
یا MAX_FOO
ممکن است در آینده تغییر کند، به جای آن روشهای پویا را در نظر بگیرید.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
سازگاری فوروارد را برای تماسهای برگشتی در نظر بگیرید
ثابتهای تعریفشده در نسخههای API آینده برای برنامههایی که APIهای قدیمیتر را هدف قرار میدهند، شناخته شده نیستند. به همین دلیل، ثابتهای ارائهشده به برنامهها باید نسخه API هدف برنامه را در نظر بگیرند و ثابتهای جدیدتر را به یک مقدار ثابت ترسیم کنند. سناریوی زیر را در نظر بگیرید:
منبع SDK فرضی:
// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;
برنامه فرضی با targetSdkVersion="22"
:
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
در این مورد، برنامه با محدودیت های سطح 22 API طراحی شده بود و یک فرض معقول (تا حدودی) ایجاد کرد که تنها دو حالت ممکن وجود دارد. با این حال، اگر برنامه STATUS_FAILURE_RETRY
جدید اضافه شده را دریافت کند، این را به عنوان موفقیت تفسیر می کند.
روشهایی که ثابتها را برمیگردانند، میتوانند با محدود کردن خروجی خود برای مطابقت با سطح API مورد نظر برنامه، مواردی مانند این را با خیال راحت مدیریت کنند:
private int mapResultForTargetSdk(Context context, int result) {
int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < 26) {
if (result == STATUS_FAILURE_ABORT) {
return STATUS_FAILURE;
}
if (targetSdkVersion < 23) {
if (result == STATUS_FAILURE_RETRY) {
return STATUS_FAILURE;
}
}
}
return result;
}
توسعه دهندگان نمی توانند پیش بینی کنند که آیا لیستی از ثابت ها ممکن است در آینده تغییر کند یا خیر. اگر یک API را با یک ثابت UNKNOWN
یا UNSPECIFIED
تعریف کنید که شبیه به همه چیز است، توسعهدهندگان فرض میکنند که ثابتهای منتشر شده هنگام نوشتن برنامهشان جامع هستند. اگر مایل به تنظیم این انتظار نیستید، مجدداً در نظر بگیرید که آیا ثابت کردن همه چیز ایده خوبی برای API شما است یا خیر.
علاوه بر این، کتابخانه ها نمی توانند targetSdkVersion
خود را جدا از برنامه مشخص کنند و مدیریت تغییرات رفتار targetSdkVersion
از کد کتابخانه پیچیده و مستعد خطا است.
عدد صحیح یا ثابت رشته
اگر فضای نام مقادیر در خارج از بسته شما قابل توسعه نیست، از ثابت های عدد صحیح و @IntDef
استفاده کنید. اگر فضای نام مشترک است یا می توان آن را با کد خارج از بسته شما گسترش داد، از ثابت های رشته استفاده کنید.
کلاس های داده
کلاسهای داده مجموعهای از ویژگیهای تغییرناپذیر را نشان میدهند و مجموعه کوچک و مشخصی از توابع کاربردی را برای تعامل با آن داده ارائه میدهند.
data class
در API های عمومی Kotlin استفاده نکنید ، زیرا کامپایلر Kotlin API زبان یا سازگاری باینری را برای کدهای تولید شده تضمین نمی کند. در عوض، توابع مورد نیاز را به صورت دستی پیاده سازی کنید.
نمونه سازی
در جاوا، کلاسهای داده باید زمانی که ویژگیهای کمی وجود دارد یک سازنده ارائه دهند یا زمانی که ویژگیهای زیادی وجود دارد از الگوی Builder
استفاده کنند.
در کاتلین، کلاسهای داده باید یک سازنده با آرگومانهای پیشفرض بدون توجه به تعداد ویژگیها ارائه کنند. کلاس های داده تعریف شده در Kotlin همچنین ممکن است از ارائه سازنده هنگام هدف قرار دادن مشتریان جاوا بهره مند شوند.
اصلاح و کپی
در مواردی که داده ها نیاز به اصلاح دارند، یک کلاس Builder
با سازنده کپی (جاوا) یا یک تابع عضو copy()
(Kotlin) ارائه دهید که یک شی جدید را برمی گرداند.
هنگام ارائه یک تابع copy()
در Kotlin، آرگومانها باید با سازنده کلاس مطابقت داشته باشند و پیشفرضها باید با استفاده از مقادیر فعلی شی پر شوند:
class Typography(
val labelMedium: TextStyle = TypographyTokens.LabelMedium,
val labelSmall: TextStyle = TypographyTokens.LabelSmall
) {
fun copy(
labelMedium: TextStyle = this.labelMedium,
labelSmall: TextStyle = this.labelSmall
): Typography = Typography(
labelMedium = labelMedium,
labelSmall = labelSmall
)
}
رفتارهای اضافی
کلاس های داده باید هم equals()
و hashCode()
پیاده سازی کنند و هر ویژگی باید در پیاده سازی این متدها در نظر گرفته شود.
کلاسهای داده میتوانند toString()
با قالب پیشنهادی مطابق با پیادهسازی کلاس داده Kotlin پیادهسازی کنند، برای مثال User(var1=Alex, var2=42)
.
روش ها
اینها قوانینی در مورد ویژگی های مختلف در متدها، پیرامون پارامترها، نام روش ها، انواع برگشتی و مشخص کننده های دسترسی هستند.
زمان
این قوانین نحوه بیان مفاهیم زمانی مانند تاریخ و مدت زمان را در APIها پوشش می دهند.
در صورت امکان، انواع java.time.* را ترجیح دهید
java.time.Duration
، java.time.Instant
و بسیاری دیگر از انواع java.time.*
در تمام نسخه های پلتفرم از طریق desgaring در دسترس هستند و باید هنگام بیان زمان در پارامترهای API یا مقادیر بازگشتی ترجیح داده شوند.
ترجیح میدهید فقط انواع یک API را نشان دهید که java.time.Duration
یا java.time.Instant
را میپذیرد یا برمیگرداند. Instant و از انواع اولیه با همان حالت استفاده حذف میشود، مگر اینکه دامنه API دامنهای باشد که تخصیص شی در الگوهای استفاده در نظر گرفته شده در آن تأثیر زیادی بر عملکرد داشته باشد.
روش هایی که مدت زمان را بیان می کنند باید مدت نامگذاری شوند
اگر مقدار زمانی بیانگر مدت زمان است، پارامتر را "دوره" نامگذاری کنید نه "زمان".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
استثنائات:
"تایم اوت" زمانی مناسب است که مدت زمان به طور خاص برای یک مقدار مهلت زمانی اعمال شود.
"time" با یک نوع java.time.Instant
زمانی مناسب است که به یک نقطه خاص در زمان اشاره شود، نه مدت زمان.
روش هایی که مدت زمان یا زمان را به صورت اولیه بیان می کنند باید با واحد زمان نامگذاری شوند و از طولانی استفاده شوند
روشهایی که مدت زمانها را بهعنوان ابتدایی میپذیرند یا برمیگردانند، باید نام روش را با واحدهای زمانی مرتبط (مانند Millis
، Nanos
، Seconds
) پسوند کنند تا نام تزئیننشده برای استفاده با java.time.Duration
ذخیره شود. زمان را ببینید.
روش ها همچنین باید به طور مناسب با واحد و مبنای زمانی خود حاشیه نویسی شوند:
-
@CurrentTimeMillisLong
: مقدار یک مهر زمانی غیرمنفی است که به عنوان تعداد میلی ثانیه از سال 1970-01-01T00:00:00Z اندازه گیری می شود. -
@CurrentTimeSecondsLong
: مقدار یک مُهر زمانی غیرمنفی است که بهعنوان تعداد ثانیهها از 1970-01-01T00:00:00Z اندازهگیری میشود. -
@DurationMillisLong
: مقدار یک مدت زمان غیرمنفی بر حسب میلی ثانیه است. -
@ElapsedRealtimeLong
: Value یک مهر زمانی غیرمنفی در پایگاه زمانی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
: Value یک مهر زمانی غیرمنفی در پایگاه زمانیSystemClock#elapsedRealtime()
است. -
@UptimeMillisLong
: مقدار یک مهر زمانی غیرمنفی در پایگاه زمانیSystemClock#uptimeMillis()
است.
واحدهای اندازه گیری
برای همه روش هایی که واحد اندازه گیری غیر از زمان را بیان می کنند، پیشوندهای واحد CamelCased SI را ترجیح دهید.
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);
هنگام اضافه کردن بارهای اضافه برای آرگومان های اختیاری، رفتار روش های ساده تر باید دقیقاً به همان شکلی باشد که گویی آرگومان های پیش فرض برای روش های دقیق تر ارائه شده است.
نتیجه: اگر روش چند شکلی است، روشهای دیگری را اضافه نکنید، مگر اینکه آرگومانهای اختیاری را اضافه کنید یا انواع مختلف آرگومانها را بپذیرید. اگر روش overloaded کاری اساساً متفاوت انجام می دهد، نام جدیدی به آن بدهید.
روشهای دارای پارامترهای پیشفرض باید با @JvmOverloads حاشیهنویسی شوند (فقط Kotlin)
متدها و سازندههای دارای پارامترهای پیشفرض باید با @JvmOverloads
حاشیهنویسی شوند تا سازگاری باینری حفظ شود.
برای جزئیات بیشتر، اضافهبارهای عملکرد را برای پیشفرضها در راهنمای رسمی interop 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 برای ایجاد اشیاء پیچیده جاوا توصیه می شود و معمولاً در اندروید برای موارد زیر استفاده می شود:
- ویژگی های شی حاصل باید تغییرناپذیر باشد
- تعداد زیادی ویژگی مورد نیاز وجود دارد، به عنوان مثال بسیاری از آرگومان های سازنده
- یک رابطه پیچیده بین خواص در زمان ساخت وجود دارد، به عنوان مثال یک مرحله تأیید لازم است. توجه داشته باشید که این سطح از پیچیدگی اغلب نشان دهنده مشکلاتی در قابلیت استفاده API است.
در نظر بگیرید که آیا به سازنده نیاز دارید یا خیر. سازندگان در سطح API مفید هستند اگر از موارد زیر استفاده کنند:
- تنها تعداد کمی از یک مجموعه بالقوه بزرگ از پارامترهای ایجاد اختیاری را پیکربندی کنید
- بسیاری از پارامترهای ایجاد اختیاری یا مورد نیاز مختلف را پیکربندی کنید، گاهی اوقات از انواع مشابه یا مشابه، که در غیر این صورت سایت های تماس ممکن است خواندن گیج کننده یا مستعد خطا برای نوشتن شوند.
- ایجاد یک شی را به صورت تدریجی پیکربندی کنید، جایی که چندین قطعه مختلف کد پیکربندی ممکن است هر کدام به عنوان جزئیات پیاده سازی با سازنده تماس بگیرند.
- با افزودن پارامترهای ایجاد اختیاری اضافی در نسخههای API آینده، به یک نوع اجازه دهید رشد کند
اگر یک نوع با سه یا کمتر پارامتر مورد نیاز و بدون پارامتر اختیاری دارید، تقریباً همیشه میتوانید از یک سازنده صرفنظر کنید و از یک سازنده ساده استفاده کنید.
کلاس های منبع کاتلین باید سازنده های حاشیه نویسی @JvmOverloads
با آرگومان های پیش فرض را به سازندگان ترجیح دهند، اما ممکن است با ارائه Builders در مواردی که قبلا ذکر شد، قابلیت استفاده را برای مشتریان جاوا بهبود بخشند.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class 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);
}
کلاس های سازنده باید از طریق سازنده ایجاد شوند
برای حفظ یکنواختی ایجاد سازنده از طریق سطح API Android، همه سازندگان باید از طریق یک سازنده و نه یک روش ایجاد کننده ایستا ایجاد شوند. برای API های مبتنی بر 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 را برای ویژگیهای اختیاری بگیرند
معمولاً استفاده از یک مقدار nullable برای ورودی درجه دوم سادهتر است، به خصوص در Kotlin، که از آرگومانهای پیشفرض به جای سازندهها و اضافهبارها استفاده میکند.
علاوه بر این، تنظیمکنندههای @Nullable
آنها را با دریافتکنندههایشان مطابقت میدهند، که برای ویژگیهای اختیاری باید @Nullable
باشد.
Value createValue(@Nullable OptionalValue optionalValue) {
Value.Builder builder = new Value.Builder();
if (optionalValue != null) {
builder.setOptionalValue(optionalValue);
}
return builder.build();
}
Value createValue(@Nullable OptionalValue optionalValue) {
return new Value.Builder()
.setOptionalValue(optionalValue);
.build();
}
// Or in other cases:
Value createValue() {
return new Value.Builder()
.setOptionalValue(condition ? new OptionalValue() : null);
.build();
}
استفاده رایج در Kotlin:
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
مقدار پیشفرض (اگر تنظیمکننده فراخوانی نشده باشد)، و معنای null
، باید بهدرستی در تنظیمکننده و دریافتکننده ثبت شوند.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
تنظیمکنندههای سازنده را میتوان برای ویژگیهای قابل تغییر در جایی که تنظیمکنندهها در کلاس ساخته شده در دسترس هستند، ارائه کرد
اگر کلاس شما دارای ویژگی های قابل تغییر است و به کلاس Builder
نیاز دارد، ابتدا از خود بپرسید که آیا کلاس شما واقعاً باید ویژگی های قابل تغییر داشته باشد یا خیر.
در مرحله بعد، اگر مطمئن هستید که به ویژگی های قابل تغییر نیاز دارید، تصمیم بگیرید که کدام یک از سناریوهای زیر برای مورد استفاده مورد انتظار شما بهتر عمل می کند:
شی ساخته شده باید فورا قابل استفاده باشد، بنابراین تنظیم کننده ها باید برای همه ویژگی های مرتبط، اعم از تغییرپذیر یا غیرقابل تغییر، ارائه شوند.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
ممکن است قبل از اینکه شی ساخته شده مفید باشد، نیاز به انجام برخی فراخوانی های اضافی باشد، بنابراین تنظیم کننده ها نباید برای ویژگی های قابل تغییر ارائه شوند.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
این دو سناریو را با هم مخلوط نکنید.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
سازندگان نباید گیرنده داشته باشند
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()
استفاده کند.
از کلاس های سازنده انتظار می رود که متد build() را اعلام کنند
کلاس های سازنده باید یک متد build()
را اعلام کنند که نمونه ای از شی ساخته شده را برمی گرداند.
متدهای build() باید اشیاء @NonNull را برگردانند
انتظار می رود متد build()
سازنده یک نمونه غیر تهی از شی ساخته شده را برگرداند. در صورتی که شیء به دلیل پارامترهای نامعتبر ایجاد نشود، اعتبارسنجی را می توان به روش ساخت موکول کرد و یک IllegalStateException
باید پرتاب شود.
قفل های داخلی را در معرض دید قرار ندهید
روشها در API عمومی نباید از کلمه کلیدی synchronized
استفاده کنند. این کلمه کلیدی باعث می شود از شی یا کلاس شما به عنوان قفل استفاده شود، و چون در معرض دیگران قرار می گیرد، اگر کد دیگری خارج از کلاس شما شروع به استفاده از آن برای مقاصد قفل کند، ممکن است با عوارض جانبی غیرمنتظره مواجه شوید.
در عوض، هر قفل مورد نیاز را در برابر یک شی داخلی و خصوصی انجام دهید.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
روشهای مبتنی بر Accessor باید از دستورالعملهای ویژگی Kotlin پیروی کنند
هنگامی که از منابع Kotlin مشاهده میشود، روشهای دارای سبک دسترسی - آنهایی که از پیشوندهای get
، set
یا is
استفاده میکنند، بهعنوان ویژگیهای Kotlin نیز در دسترس خواهند بود. برای مثال، int getField()
تعریف شده در جاوا در Kotlin به عنوان val field: Int
.
به همین دلیل، و به طور کلی برای برآورده کردن انتظارات توسعهدهندگان در مورد رفتار متد دسترسی، روشهایی که از پیشوندهای متد دسترسی استفاده میکنند باید مانند فیلدهای جاوا رفتار کنند. زمانی که:
- این روش دارای عوارض جانبی است - نام روش توصیفی تر را ترجیح دهید
- این روش شامل کار محاسباتی پرهزینه است --
compute
ترجیح دهید - این روش شامل مسدود کردن یا کارهای طولانی مدت برای برگرداندن یک مقدار، مانند IPC یا سایر ورودی/خروجی ها است -
fetch
ترجیح می دهند. - متد تا زمانی که بتواند مقداری را برگرداند رشته را مسدود میکند -
await
ترجیح دهید - این متد در هر فراخوانی یک نمونه شی جدید برمی گرداند -- ترجیح
create
- این روش ممکن است با موفقیت مقداری را برنگرداند -
request
ترجیح دهید
توجه داشته باشید که انجام یک کار گران قیمت محاسباتی و ذخیره کردن مقدار برای تماسهای بعدی همچنان به عنوان انجام کارهای گران قیمت محاسباتی محسوب میشود. جانک بین فریم ها مستهلک نمی شود.
استفاده پیشوند برای متدهای دسترسی بولی است
این قرارداد نامگذاری استاندارد برای روش ها و فیلدهای بولی در جاوا است. به طور کلی، نام متدهای بولی و متغیرها باید به عنوان سؤالاتی نوشته شوند که با مقدار بازگشتی پاسخ داده شوند.
متدهای دسترسی بولین جاوا باید از یک طرح نامگذاری 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
متدهای دسترسی جاوا یا برای فیلدهای جاوا is
به آنها اجازه می دهد تا به عنوان ویژگی های Kotlin استفاده شوند:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
ویژگیها و روشهای دسترسی عموماً باید از نامگذاری مثبت استفاده کنند، برای مثال Enabled
به جای Disabled
. استفاده از اصطلاحات منفی معنای true
و false
را معکوس می کند و استدلال در مورد رفتار را دشوارتر می کند.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
در مواردی که بولی گنجاندن یا مالکیت یک ملک را توصیف می کند، می توانید از has به جای is استفاده کنید. با این حال، این با نحو ویژگی Kotlin کار نخواهد کرد:
// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();
برخی از پیشوندهای جایگزین که ممکن است مناسب تر باشند عبارتند از can و should :
// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();
// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();
روش هایی که رفتارها یا ویژگی ها را تغییر می دهند ممکن است از پیشوند is و پسوند Enabled استفاده کنند:
// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()
به طور مشابه، روشهایی که نشاندهنده وابستگی به سایر رفتارها یا ویژگیهایی است که ممکن است استفاده شود، پیشوند و پسوند پشتیبانی شده یا مورد نیاز است :
// "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()
به طور کلی، نام روش ها باید به عنوان سؤالاتی نوشته شوند که با مقدار بازگشتی پاسخ داده می شوند.
روش های ویژگی کاتلین
برای یک ویژگی کلاس var foo: Foo
Kotlin روش های get
/ set
را با استفاده از یک قانون سازگار ایجاد می کند: پیش بینی کنید و شخصیت اول را برای گیرنده get
و set
آماده کنید و شخصیت اول را برای تنظیم کننده تهیه کنید. در بیانیه املاک به ترتیب روش هایی به نام public Foo getFoo()
و public void setFoo(Foo foo)
تولید می کند.
اگر این ملک از نوع Boolean
باشد ، یک قانون اضافی در تولید نام اعمال می شود: اگر نام ویژگی با is
شروع شود ، get
برای نام روش Getter پیش بینی نمی شود ، خود نام ویژگی به عنوان گیرنده استفاده می شود. بنابراین ، برای پیروی از دستورالعمل نامگذاری ، خصوصیات Boolean
را با پیشوند is
ترجیح می دهید :
var isVisible: Boolean
اگر ملک شما یکی از استثنائات فوق الذکر است و با پیشوند مناسب شروع می شود ، از حاشیه نویسی @get:JvmName
در این ملک استفاده کنید تا نام مناسب را مشخص کنید:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
لوازم جانبی بیت ماسک
به استفاده از @IntDef
برای پرچم های bitmask برای دستورالعمل های API در مورد تعریف پرچم های bitmask مراجعه کنید.
متصدی
باید دو روش تنظیم کننده ارائه شود: یکی که یک بیتستر کامل را می گیرد و تمام پرچم های موجود را بازنویسی می کند و دیگری که یک bitmask سفارشی را می گیرد تا انعطاف پذیری بیشتری امکان پذیر باشد.
/**
* Sets the state of all scroll indicators.
* <p>
* See {@link #setScrollIndicators(int, int)} for usage information.
*
* @param indicators a bitmask of indicators that should be enabled, or
* {@code 0} to disable all indicators
* @see #setScrollIndicators(int, int)
* @see #getScrollIndicators()
*/
public void setScrollIndicators(@ScrollIndicators int indicators);
/**
* Sets the state of the scroll indicators specified by the mask. To change
* all scroll indicators at once, see {@link #setScrollIndicators(int)}.
* <p>
* When a scroll indicator is enabled, it will be displayed if the view
* can scroll in the direction of the indicator.
* <p>
* Multiple indicator types may be enabled or disabled by passing the
* logical OR of the specified types. If multiple types are specified, they
* will all be set to the same enabled state.
* <p>
* For example, to enable the top scroll indicator:
* {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
* <p>
* To disable the top scroll indicator:
* {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
*
* @param indicators a bitmask of values to set; may be a single flag,
* the logical OR of multiple flags, or 0 to clear
* @param mask a bitmask indicating which indicator flags to modify
* @see #setScrollIndicators(int)
* @see #getScrollIndicators()
*/
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);
گیرندگان
برای به دست آوردن بیت ماسک کامل باید یک گیرنده ارائه شود.
/**
* Returns a bitmask representing the enabled scroll indicators.
* <p>
* For example, if the top and left scroll indicators are enabled and all
* other indicators are disabled, the return value will be
* {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
* <p>
* To check whether the bottom scroll indicator is enabled, use the value
* of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
*
* @return a bitmask representing the enabled scroll indicators
*/
@ScrollIndicators
public int getScrollIndicators();
به جای محافظت از عموم مردم استفاده کنید
همیشه public
را به protected
در API عمومی ترجیح می دهند. دسترسی محافظت شده در طولانی مدت دردناک است ، زیرا مجریان برای تأمین دسترسی عمومی در مواردی که دسترسی خارجی به طور پیش فرض به همان اندازه خوب باشد ، باید از آنها پیروز شوند.
به یاد داشته باشید که دید protected
مانع از ایجاد API توسعه دهندگان نمی شود - این فقط باعث می شود آن را کمی مبهم تر کند.
اجرای هیچ یک یا هر دو برابر () و HASHCODE ()
اگر یکی را نادیده بگیرید ، باید دیگری را نادیده بگیرید.
پیاده سازی toString() برای کلاس های داده
کلاس های داده تشویق می شوند تا از toString()
، برای کمک به توسعه دهندگان اشکال در کد خود استفاده کنند.
مستندسازی کنید که آیا خروجی مربوط به رفتار برنامه است یا اشکال زدایی
تصمیم بگیرید که آیا می خواهید رفتار برنامه به اجرای شما اعتماد کند یا خیر. به عنوان مثال ، UUID.TOSTRING () و FILE.TOSTRING () قالب خاص خود را برای برنامه هایی برای استفاده مستند می کنند. اگر شما فقط برای اشکال زدایی ، مانند قصد ، اطلاعاتی را در معرض دید قرار می دهید ، پس از آن اسناد ارث را از سوپر کلاس دلالت می کنید.
اطلاعات اضافی را درج نکنید
تمام اطلاعات موجود از toString()
نیز باید از طریق API عمومی شیء در دسترس باشد. در غیر این صورت ، شما توسعه دهندگان را ترغیب می کنید تا به خروجی toString()
خود متکی باشند ، که از تغییرات آینده جلوگیری می کند. یک عمل خوب اجرای toString()
فقط با استفاده از API عمومی شیء است.
اعتماد به خروجی اشکال زدایی را دلسرد می کند
در حالی که جلوگیری از بسته به خروجی اشکال زدایی ، از جمله System.identityHashCode
غیرممکن است. 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
s. برای فعال کردن پردازش منابع مختلف داده ، API هایی که اشیاء 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)
اجتناب از معادل های کلاس از انواع بدوی ، از سربار حافظه این کلاس ها ، دسترسی به روش به مقادیر و مهمتر از همه ، autoboxing که ناشی از ریخته گری بین انواع بدوی و شی است ، جلوگیری می کند. اجتناب از این رفتارها در حافظه و تخصیص موقت که می تواند منجر به مجموعه زباله های گران قیمت و مکرر شود ، صرفه جویی می شود.
از حاشیه نویسی برای روشن کردن پارامترهای معتبر و مقادیر برگشتی استفاده کنید
حاشیه نویسی توسعه دهنده برای کمک به روشن کردن مقادیر مجاز در موقعیت های مختلف اضافه شد. این امر باعث می شود ابزارها بتوانند هنگام تهیه مقادیر نادرست ، به توسعه دهندگان کمک کنند (به عنوان مثال ، عبور از int
دلخواه در هنگام چارچوب به یکی از مجموعه های خاص از مقادیر ثابت نیاز دارد). در صورت لزوم از هرگونه حاشیه نویسی زیر استفاده کنید:
پوچ پذیری
حاشیه نویسی صریح برای API های جاوا مورد نیاز است ، اما مفهوم تهی بخشی از زبان کوتلین است و حاشیه نویسی های تهی هرگز نباید در API های Kotlin استفاده شود.
@Nullable
: نشان می دهد که یک مقدار بازگشت داده شده ، پارامتر یا زمینه می تواند تهی باشد:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: نشان می دهد که یک مقدار برگشتی ، پارامتر یا قسمت مشخص نمی تواند تهی باشد. علامت گذاری به عنوان @Nullable
برای اندروید نسبتاً جدید است ، بنابراین بیشتر روشهای API Android به طور مداوم ثبت نمی شوند. بنابراین ما یک حالت سه گانه از "ناشناخته ، @Nullable
، @NonNull
" داریم که به همین دلیل @NonNull
بخشی از دستورالعمل های API است:
@NonNull
public String getName()
public void setName(@NonNull String name)
برای اسناد پلت فرم Android ، حاشیه نویسی پارامترهای روش شما به طور خودکار مستندات را در فرم "این مقدار ممکن است تهی کند" تولید می کند. مگر اینکه "تهی" به صراحت در جای دیگری در Doc پارامتر استفاده شود.
روشهای "واقعاً تهی" موجود نیست: روشهای موجود در API بدون حاشیه نویسی اعلام @Nullable
ممکن است در صورتی که این روش بتواند null
تحت شرایط خاص و آشکار (مانند findViewById()
) برگرداند @Nullable
باشد. Companionnotnull @NotNull requireFoo()
روش هایی که IllegalArgumentException
پرتاب می کنند ، باید برای توسعه دهندگان که نمی خواهند بررسی کنند ، اضافه شود.
روش های رابط: API های جدید باید هنگام اجرای روش های رابط ، مانند Parcelable.writeToParcel()
(به عنوان مثال ، آن روش در کلاس اجرایی ، باید writeToParcel(@NonNull Parcel, int)
باشد ، نه writeToParcel(Parcel, int)
). API های موجود که فاقد حاشیه نویسی هستند ، نیازی به "ثابت" ندارند.
اجرای
در جاوا ، روش هایی برای انجام اعتبار سنجی ورودی برای پارامترهای @NonNull
با استفاده NullPointerException
Objects.requireNonNull()
توصیه می شود. این به طور خودکار در کوتلین انجام می شود.
منابع
شناسه منابع: پارامترهای عدد صحیح که شناسه های منابع خاص را نشان می دهند باید با تعریف نوع منابع مناسب حاشیه نویسی شوند. علاوه بر Catch- @AnyRes
، یک حاشیه نویسی برای هر نوع منبع ، مانند @StringRes
، @ColorRes
و @AnimRes
وجود دارد. به عنوان مثال:
public void setTitle(@StringRes int resId)
@IntDef برای مجموعه های ثابت
ثابت های جادویی: پارامترهای String
و int
که به معنای دریافت یکی از مجموعه های محدود از مقادیر ممکن است که توسط ثابت های عمومی مشخص شده است باید به طور مناسب با @StringDef
یا @IntDef
حاشیه نویسی شود. این حاشیه نویسی ها به شما امکان می دهد حاشیه نویسی جدیدی ایجاد کنید که می توانید از آن استفاده کنید که مانند یک Typedef برای پارامترهای مجاز کار می کند. به عنوان مثال:
/** @hide */
@IntDef(prefix = {"NAVIGATION_MODE_"}, value = {
NAVIGATION_MODE_STANDARD,
NAVIGATION_MODE_LIST,
NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}
public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;
@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);
روشها برای بررسی اعتبار پارامترهای حاشیه نویسی و پرتاب IllegalArgumentException
در صورتی که پارامتر جزئی از @IntDef
نباشد ، توصیه می شود.
intdef برای پرچم های bitmask
حاشیه نویسی همچنین می تواند مشخص کند که ثابت ها پرچم هستند و می توانند با & و 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 Annotate زمینه های عمومی هنگامی که آنها یکی از این مقادیر 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";
قابلیت پوچ پذیری سازگار را برای بازنویسی ها فراهم کنید
برای سازگاری API ، تهویه ناپایدار باید با تهی فعلی والدین سازگار باشد. جدول زیر انتظارات سازگاری را نشان می دهد. به طور واضح ، نادیده گرفتن فقط باید به همان اندازه محدود کننده یا محدود کننده تر از عنصری باشد که بر آن غلبه می کنند.
تایپ کنید | پدر و مادر | کودک |
---|---|---|
نوع برگشت | بدون قید نشده | بدون آرامی یا غیرقانونی |
نوع برگشت | باطل شدنی | قابل برگشت یا غیر قابل برگشت |
نوع برگشت | ناله | ناله |
استدلال سرگرم کننده | بدون قید نشده | بدون قند |
استدلال سرگرم کننده | باطل شدنی | باطل شدنی |
استدلال سرگرم کننده | ناله | قابل برگشت یا غیر قابل برگشت |
آرگومان های غیر قابل قبول (مانند 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() { ... }
حاشیه نویسی های تهی برای جفت های دریافت و تنظیم باید موافق باشند
جفت های روش را برای یک خاصیت منطقی واحد دریافت و تنظیم کنید ، همیشه باید در حاشیه نویسی های تهی خود موافق باشند. عدم پیروی از این راهنما ، نحو خاصیت کوتلین را شکست می دهد ، و افزودن حاشیه نویسی مخالف به روش های خاصیت موجود ، یک تغییر منبع برای کاربران کوتلین است.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
مقدار بازگشت در شرایط خرابی یا خطا
همه API ها باید به برنامه ها اجازه دهند که نسبت به خطاها واکنش نشان دهند. بازگشت false
، -1
، null
یا سایر مقادیر "همه چیز اشتباه" به یک توسعه دهنده در مورد عدم تنظیم انتظارات کاربر یا پیگیری دقیق برنامه خود در این زمینه ، به یک توسعه دهنده بگویید. هنگام طراحی API ، تصور کنید که در حال ساخت یک برنامه هستید. اگر با خطایی روبرو شدید ، آیا API اطلاعات کافی را برای ارائه آن به کاربر ارائه می دهد یا به طور مناسب واکنش نشان می دهد؟
- خوب است (و تشویق می شود) اطلاعات مفصلی را در یک پیام استثناء گنجانده باشد ، اما توسعه دهندگان برای رسیدگی مناسب به خطا مجبور نیستند آن را تجزیه کنند. کدهای خطای Verbose یا سایر اطلاعات باید به عنوان روش در معرض دید قرار بگیرند.
- اطمینان حاصل کنید که گزینه انتخاب خطای انتخاب شده شما به شما انعطاف پذیری می دهد تا در آینده انواع خطای جدید را معرفی کنید. برای
@IntDef
، این بدان معنی است که شامل یک مقدارOTHER
یاUNKNOWN
- هنگام بازگشت کد جدید ، می توانیدtargetSdkVersion
تماس گیرنده را بررسی کنید تا از بازگشت کد خطایی که برنامه در مورد آن نمی داند جلوگیری کنید. برای استثنائات ، یک ابرقهرمان مشترک را که استثنائات شما را پیاده سازی می کند ، داشته باشید ، به طوری که هر کدی که دارای نوع باشد نیز می تواند زیرگروه ها را بدست آورد و کنترل کند. - برای یک توسعه دهنده دشوار یا غیرممکن است که به طور تصادفی خطایی را نادیده بگیرد - اگر خطای شما با بازگشت یک مقدار به شما ابلاغ می شود ، روش خود را با
@CheckResult
حاشیه نویسی کنید.
ترجیح می دهید پرتاب کنید ? extends RuntimeException
، به عنوان مثال نادیده گرفتن محدودیت در پارامترهای ورودی یا عدم بررسی وضعیت مشاهده.
روشهای تنظیم کننده یا عمل (به عنوان مثال ، perform
) ممکن است یک کد وضعیت عدد صحیح را برگرداند اگر عمل ممکن است در نتیجه وضعیت یا شرایط به روز شده ناهمزمان به طور ناهمزمان انجام شود.
کدهای وضعیت باید در کلاس حاوی به عنوان قسمتهای public static final
@IntDef
شوند ، پیشوند با ERROR_
و در حاشیه نویسی @hide
ذکر شده باشند.
نام های روش همیشه باید با فعل شروع شود ، نه موضوع
نام روش همیشه باید با فعل (مانند get
، create
، reload
و غیره) آغاز شود ، نه شیئی که روی آن عمل می کنید.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
مجموعه را ترجیح می دهد انواع آرایه ها به عنوان نوع بازگشت یا پارامتر
رابط های جمع آوری شده به طور کلی مزایای مختلفی را نسبت به آرایه ها ارائه می دهند ، از جمله قراردادهای API قوی تر در مورد منحصر به فرد بودن و سفارش ، پشتیبانی از ژنرال ها و تعدادی از روشهای راحتی سازگار با توسعه دهنده.
استثنا برای اولیه ها
اگر عناصر ابتدایی هستند ، به جای آن ، آرایه ها را ترجیح می دهند تا از هزینه جعبه خودکار جلوگیری شود. به جای نسخه های جعبه ای ، ابتدایی خام را ببینید و برگردانید
استثنا برای کدهای حساس به عملکرد
در سناریوهای خاص ، جایی که از API در کد حساس به عملکرد استفاده می شود (مانند گرافیک یا API های اندازه گیری/طرح بندی/ترسیم) ، استفاده از آرایه ها به جای مجموعه ها به منظور کاهش تخصیص و خفه کردن حافظه قابل قبول است.
استثنا برای کوتلین
آرایه های کوتلین متغیر هستند و زبان کوتلین API های ابزارهای زیادی را در اطراف آرایه ها فراهم می کند ، بنابراین آرایه ها با List
و Collection
API های 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;
}
تغییرپذیری مجموعه
API های Kotlin باید به طور پیش فرض انواع بازده فقط خواندنی (قابل Mutable
) را برای مجموعه ها ترجیح دهند ، مگر اینکه قرارداد API به طور خاص به یک نوع بازده قابل تغییر نیاز داشته باشد.
با این حال ، API های جاوا باید به طور پیش فرض انواع بازده قابل تغییر را ترجیح دهند زیرا اجرای پلت فرم Android API های جاوا هنوز اجرای مناسب مجموعه های تغییر ناپذیر را ارائه نمی دهد. استثناء این قانون ، Collections.empty
هستند. انواع بازده خالی ، که تغییر ناپذیر هستند. در مواردی که می توان از جهش پذیری توسط مشتری ها استفاده کرد - به طور هدف یا اشتباه - برای شکستن الگوی استفاده در نظر گرفته شده API ، API های جاوا باید به شدت در نظر بگیرند که یک نسخه کم عمق از مجموعه را برگردانند.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
انواع بازگشتی که به وضوح قابل تغییر هستند
API هایی که مجموعه های برگشتی باید به طور ایده آل پس از بازگشت ، شیء مجموعه برگشتی را تغییر دهند. اگر مجموعه برگشتی باید به نوعی تغییر کند یا مورد استفاده مجدد قرار گیرد - به عنوان مثال ، یک دیدگاه سازگار از یک مجموعه داده قابل تغییر - رفتار دقیق زمانی که محتویات می توانند تغییر کنند باید صریحاً مستند شوند یا از کنوانسیون های تعیین شده API پیروی کنند.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
کنوانسیون Kotlin .asFoo()
در زیر شرح داده شده است و در صورت تغییر مجموعه اصلی ، مجموعه ای را که توسط .asList()
تغییر یافته است ، اجازه می دهد تا تغییر کند.
تغییرپذیری اشیاء از نوع داده های برگشتی
مشابه API هایی که مجموعه ها را برمی گردانند ، API هایی که اشیاء از نوع داده ها را برمی گردانند ، باید به طور ایده آل پس از بازگشت ، خصوصیات شیء برگشتی را اصلاح کنند.
val tempResult = DataContainer()
fun add(other: DataContainer): DataContainer {
tempResult.innerValue = innerValue + other.innerValue
return tempResult
}
fun add(other: DataContainer): DataContainer {
return DataContainer(innerValue + other.innerValue)
}
در موارد بسیار محدود ، برخی از کد های حساس به عملکرد ممکن است از جمع شدن شیء یا استفاده مجدد بهره مند شوند. ساختار داده استخر شیء خود را ننویسید و اشیاء مورد استفاده مجدد را در API های عمومی افشا نکنید . در هر صورت ، در مدیریت دسترسی همزمان بسیار مراقب باشید.
استفاده از نوع پارامتر Vararg
هر دو API های Kotlin و Java به استفاده از vararg
در مواردی که توسعه دهنده احتمالاً می تواند آرایه ای را در محل تماس با هدف عبور از پارامترهای متعدد و مرتبط با همان نوع ایجاد کند ، تشویق می شوند.
public void setFeatures(Feature[] features) { ... }
// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }
// Developer code
setFeatures(Features.A, Features.B, Features.C);
نسخه های دفاعی
هر دو اجرای جاوا و کوتلین از پارامترهای vararg
به همان بایت کد با پشت آرایه کامپایل می شوند و در نتیجه ممکن است از کد جاوا با یک آرایه قابل تغییر فراخوانی شود. طراحان API به شدت تشویق می شوند که یک کپی کم عمق دفاعی از پارامتر آرایه را در مواردی که در یک زمینه یا کلاس داخلی ناشناس ادامه یابد ، ایجاد کنند.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
توجه داشته باشید که ایجاد یک کپی دفاعی هیچ گونه محافظت در برابر اصلاح همزمان بین فراخوانی روش اولیه و ایجاد نسخه را ارائه نمی دهد ، و همچنین در برابر جهش اشیاء موجود در آرایه محافظت نمی کند.
معانی صحیح با پارامترهای نوع مجموعه یا انواع برگشتی ارائه دهید
List<Foo>
گزینه پیش فرض است ، اما انواع دیگر را برای ارائه معنای اضافی در نظر بگیرید:
از
Set<Foo>
استفاده کنید ، اگر API شما نسبت به ترتیب عناصر بی تفاوت است و اجازه نمی دهد کپی ها یا کپی ها بی معنی باشند.Collection<Foo>,
اگر API شما نسبت به سفارش بی تفاوت است و به نسخه های کپی اجازه می دهد.
توابع تبدیل کوتلین
کوتلین اغلب از .toFoo()
و .asFoo()
برای به دست آوردن یک شیء از نوع دیگری از یک شی موجود استفاده می کند که Foo
نام نوع بازگشت تبدیل است. این با Object.toString()
. کوتلین این کار را با استفاده از آن برای تبدیل های بدوی مانند 25.toFloat()
انجام می دهد.
تمایز بین تبدیل به نام .toFoo()
و .asFoo()
قابل توجه است:
هنگام ایجاد یک شیء جدید و مستقل از .tofoo () استفاده کنید
مانند .toString()
، تبدیل "به" یک شیء جدید و مستقل را برمی گرداند. اگر بعداً شیء اصلی اصلاح شود ، شیء جدید این تغییرات را منعکس نمی کند. به همین ترتیب ، اگر شیء جدید بعداً اصلاح شود ، شیء قدیمی آن تغییرات را منعکس نمی کند.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
هنگام ایجاد یک بسته بندی وابسته، شی تزئین شده یا ریخته گری از .asFoo() استفاده کنید
ریخته گری در کوتلین با استفاده از کلمه کلیدی as
انجام می شود. این نشان دهنده تغییر در رابط است اما تغییر در هویت نیست. هنگامی که به عنوان پیشوند در یک تابع پسوند استفاده می شود ، .asFoo()
گیرنده را تزئین می کند. جهش در شیء گیرنده اصلی در شیء برگشتی توسط asFoo()
منعکس می شود. جهش در شیء جدید Foo
ممکن است در شیء اصلی منعکس شود.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
توابع تبدیل باید به عنوان توابع پسوند نوشته شود
نوشتن توابع تبدیل به خارج از گیرنده و تعاریف کلاس نتیجه ، اتصال بین انواع را کاهش می دهد. تبدیل ایده آل فقط نیاز به دسترسی عمومی به API به شیء اصلی دارد. این به عنوان مثال ثابت می کند که یک توسعه دهنده می تواند تبدیل مشابهی را به انواع دلخواه خود بنویسد.
استثنائات خاص مناسب را پرتاب کنید
روشها نباید استثنائات عمومی مانند java.lang.Exception
یا java.lang.Throwable
را پرتاب کنند ، در عوض باید یک استثناء خاص مناسب مانند java.lang.NullPointerException
استفاده شود تا به توسعه دهندگان اجازه دهد بدون اینکه بیش از حد گسترده باشند ، استثنائات را کنترل کنند.
خطاهایی که به استدلالهای ارائه شده مستقیماً به روش فراخوانی عمومی ارتباطی ندارند ، باید به جای java.lang.IllegalStateException
یا java.lang.IllegalArgumentException
، java.lang.NullPointerException
را پرتاب کنند.
شنوندگان و تماس های تماس تلفنی
این قوانین پیرامون کلاس ها و روش های مورد استفاده برای شنونده و مکانیسم های پاسخ به تماس است.
نام کلاس های برگشت به تماس باید مفرد باشد
به جای MyObjectCallback
از MyObjectCallbacks
استفاده کنید.
نام روش های برگشت به تماس باید با فرمت موجود باشد
onFooEvent
نشان می دهد که FooEvent
اتفاق می افتد و پاسخ به تماس باید در پاسخ عمل کند.
گذشته در مقابل تنش موجود باید رفتار زمان بندی را توصیف کند
روش های پاسخ به تماس در مورد وقایع باید نامگذاری شود تا نشان دهد آیا این رویداد قبلاً اتفاق افتاده است یا در حال انجام است.
به عنوان مثال ، اگر این روش پس از انجام عمل کلیک خوانده شود:
public void onClicked()
با این حال ، اگر این روش وظیفه انجام عمل کلیک را بر عهده دارد:
public boolean onClick()
ثبت نام تماس تلفنی
هنگامی که یک شنونده یا پاسخ به تماس را می توان از یک شیء اضافه یا حذف کرد ، روش های مرتبط باید نامگذاری شود و حذف یا ثبت یا ثبت نام و ثبت نام کنید. با کنوانسیون موجود که توسط کلاس یا کلاس های دیگر در همان بسته استفاده می شود ، سازگار باشد. هنگامی که چنین سابقه ای وجود ندارد ، اضافه کردن و حذف را ترجیح دهید.
روشهای مربوط به ثبت نام یا عدم ثبت نام تماسهای برگشتی باید کل نام نوع برگشتی را مشخص کند.
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()
را اضافه نکنید . این یک دریچه فرار وسوسه انگیز برای مواردی است که توسعه دهندگان ممکن است بخواهند یک تماس تلفنی موجود را به همراه جایگزینی خود زنجیر کنند ، اما شکننده است و دلیل فعلی را برای توسعه دهندگان مؤلفه دشوار می کند. به عنوان مثال،
- توسعه دهنده A
setFooCallback(a)
- توسعه دهنده B
setFooCallback(new B(getFooCallback()))
تماس می گیرد - توسعه دهنده A آرزو دارد که پاسخ به تماس خود
a
حذف کند و هیچ راهی برای انجام این کار بدون آگاهی از نوعB
ندارد ، وB
ساخته شده است تا چنین تغییراتی در پاسخ به بسته بندی شده خود فراهم شود.
برای کنترل ارسال پاسخ به تماس، Executor را بپذیرید
هنگام ثبت تماس های تماس تلفنی که انتظارات صریح و موضوعی صریح ندارند (تقریباً در هر نقطه خارج از ابزار UI) ، به شدت تشویق می شود که یک پارامتر Executor
به عنوان بخشی از ثبت نام درج کنید تا به توسعه دهنده اجازه دهد تا موضوعی را که در آن تماس تلفنی مورد استفاده قرار می گیرد ، مشخص کند.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
به عنوان یک استثناء در دستورالعمل های معمول ما در مورد پارامترهای اختیاری ، فراهم کردن یک بار اضافی که Executor
حذف می کند ، قابل قبول است حتی اگر این آرگومان نهایی در لیست پارامتر نباشد. اگر Executor
ارائه نشده باشد ، باید با استفاده از Looper.getMainLooper()
در مورد اصلی تماس بگیرید و این باید در روش اضافه بار همراه باشد.
/**
* ...
* Note that the callback will be executed on the main thread using
* {@link Looper.getMainLooper()}. To specify the execution thread, use
* {@link registerFooCallback(Executor, FooCallback)}.
* ...
*/
public void registerFooCallback(
@NonNull FooCallback callback)
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
اجرای Executor
GOTCHAS: توجه داشته باشید که موارد زیر یک مجری معتبر است!
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
این بدان معناست که هنگام اجرای API هایی که این فرم را می گیرند ، اجرای شیء اتصال دهنده شما در سمت فرآیند برنامه باید قبل از استناد به پاسخگویی برنامه در Executor
برنامه Binder.clearCallingIdentity()
تماس بگیرید. به این ترتیب هر کد برنامه ای که از هویت Binder (مانند Binder.getCallingUid()
) برای بررسی های مجوز استفاده می کند ، به درستی کد در حال اجرا به برنامه را نشان می دهد و به فرآیند سیستم فراخوانی نمی شود. اگر کاربران API شما می خواهند اطلاعات UID یا PID تماس گیرنده را بخواهند ، این باید بخشی از سطح API شما باشد ، نه اینکه بر اساس جایی که Executor
آنها را اجرا کرده است ، ضمنی باشد.
مشخص کردن یک Executor
باید توسط API شما پشتیبانی شود. در موارد مهم و مهم برنامه ها ممکن است برنامه ها را بلافاصله یا همزمان با بازخورد API خود اجرا کنند. پذیرش یک Executor
این اجازه را می دهد. از لحاظ دفاعی ایجاد یک HandlerThread
اضافی یا مشابه ترامپولین از شکست این مورد استفاده مطلوب.
اگر یک برنامه قصد دارد کد گران قیمت را در جایی در روند خاص خود اجرا کند ، به آنها اجازه دهید . راه حل هایی که توسعه دهندگان برنامه برای غلبه بر محدودیت های شما پیدا می کنند ، پشتیبانی در دراز مدت بسیار سخت تر خواهد بود.
استثناء برای پاسخ به تماس منفرد: هنگامی که ماهیت وقایع گزارش شده فقط از یک نمونه پاسخ به تماس واحد استفاده می کند ، از سبک زیر استفاده کنید:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
به جای Handler از Executor استفاده کنید
Handler
Android به عنوان استانداردی برای تغییر مسیر اجرای پاسخ به یک موضوع Looper
خاص در گذشته استفاده می شد. این استاندارد برای ترجیح Executor
تغییر یافته است زیرا بیشتر توسعه دهندگان برنامه استخرهای نخ خود را مدیریت می کنند و موضوع اصلی یا UI را تنها موضوع Looper
در دسترس برنامه قرار می دهد. از Executor
استفاده کنید تا کنترل مورد نیاز خود را برای استفاده مجدد از زمینه های اعدام موجود/ترجیحی خود به توسعه دهندگان ارائه دهید.
کتابخانه های همزمانی مدرن مانند Kotlinx.coroutines یا rxjava مکانیسم های برنامه ریزی خود را ارائه می دهند که در صورت لزوم اعزام خود را انجام می دهند و این امر باعث می شود توانایی استفاده از یک مجری مستقیم (مانند Runnable::run
) برای جلوگیری از تأخیر از هاپ دو موضوع مهم باشد. به عنوان مثال ، یک هاپ برای ارسال به یک موضوع Looper
با استفاده از یک Handler
و به دنبال آن هاپ دیگر از چارچوب همزمانی برنامه.
استثنائات این راهنما نادر است. درخواست های مشترک برای یک استثنا شامل موارد زیر است:
من مجبور هستم از یک Looper
استفاده کنم زیرا برای این رویداد به یک Looper
برای epoll
نیاز دارم. این درخواست استثناء به عنوان مزایای Executor
در این شرایط تحقق می یابد.
من نمی خواهم کد برنامه را مسدود کند تا موضوع من را منتشر کند. این درخواست استثنا به طور معمول برای کدی که در یک فرآیند برنامه اجرا می شود ، اعطا نمی شود. برنامه هایی که این مسئله را اشتباه می گیرند ، فقط به خود آسیب می رسانند و بر سلامت کلی سیستم تأثیر نمی گذارند. برنامه هایی که آن را به درستی دریافت می کنند یا از یک چارچوب همزمانی مشترک استفاده می کنند ، نباید مجازات های تأخیر اضافی را بپردازند.
Handler
به صورت محلی با سایر API های مشابه در همان کلاس سازگار است. این درخواست استثناء از نظر موقعیتی اعطا می شود. اولویت برای اضافه شدن بارهای مبتنی بر Executor
است که برای استفاده از اجرای Executor
جدید ، پیاده سازی های Handler
مهاجرت می کند. ( 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
هنگام افزودن به رابط های قبلاً منتشر شده استفاده کنند. پیش از این ، این راهنما به دلیل عدم وجود روش های default
در جاوا 7 ، abstract class
توصیه می کرد.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
از android.os.OutcomeReceiver هنگام مدلسازی فراخوانی تابع غیرمسدود کننده استفاده کنید
OutcomeReceiver<R,E>
مقدار نتیجه R
را در هنگام موفقیت یا E : Throwable
- همان کارهایی که یک روش ساده می تواند انجام دهد. هنگام تبدیل یک روش مسدود کننده که نتیجه را برمی گرداند یا یک استثنا را به یک روش غیر مسدود کننده ASYNC می اندازد ، از OutcomeReceiver
به عنوان نوع پاسخ به تماس استفاده کنید:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
روشهای async که از این طریق تبدیل شده اند ، همیشه void
باز می گردند. هر نتیجه ای که requestFoo
بازگردد ، در عوض گزارش می شود که با فراخوانی آن در executor
ارائه شده ، پارامتر پاسخگویی callback
OutcomeReceiver.onResult
requestFooAsync
. هر استثنائی که requestFoo
پرتاب می کند ، در عوض به روش OutcomeReceiver.onError
به همان روش گزارش می شود.
استفاده از OutcomeReceiver
برای گزارش نتایج روش ASYNC همچنین یک بسته بندی suspend fun
Kotlin را برای روش های ASYNC با استفاده از Continuation.asOutcomeReceiver
androidx.core:core-ktx
می دهد
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
پسوندهایی مانند این باعث می شود مشتری های Kotlin بتوانند روشهای Async را غیر مسدود کنند و با راحتی یک تماس عملکرد ساده و بدون مسدود کردن موضوع فراخوانی. این برنامه های افزودنی 1-1 برای API های پلتفرم ممکن است به عنوان بخشی از androidx.core:core-ktx
Artifact در JetPack هنگام ترکیب با بررسی های سازگاری نسخه استاندارد و ملاحظات ارائه شود. برای اطلاعات بیشتر ، ملاحظات لغو و نمونه ها ، به اسناد مربوط به AsoutComereceiver مراجعه کنید.
روشهای ASYNC که مطابق با معناشناسی یک روش بازگرداندن نتیجه یا پرتاب استثناء در هنگام کامل شدن کار آن نیست ، نباید OutcomeReceiver
به عنوان یک نوع برگشت تماس استفاده کند. در عوض یکی از گزینه های دیگر ذکر شده در بخش زیر را در نظر بگیرید.
رابط های کاربردی را نسبت به ایجاد انواع جدید روش انتزاعی تک (SAM) ترجیح دهید
API سطح 24 انواع java.util.function.*
( اسناد مرجع ) ، که رابط های عمومی SAM مانند Consumer<T>
را ارائه می دهند که برای استفاده به عنوان لامبدا پاسخ به تماس مناسب هستند. در بسیاری از موارد ، ایجاد رابط های جدید SAM از نظر ایمنی نوع یا برقراری ارتباط در حالی که به طور غیر ضروری منطقه API ANDROID را گسترش می دهد ، ارزش کمی را فراهم می کند.
به جای ایجاد موارد جدید ، استفاده از این رابط های عمومی را در نظر بگیرید:
-
Runnable
:() -> Unit
-
Supplier<R>
:() -> R
-
Consumer<T>
:(T) -> Unit
-
Function<T,R>
:(T) -> R
-
Predicate<T>
:(T) -> Boolean
- بسیاری دیگر در اسناد مرجع موجود است
قرار دادن پارامترهای SAM
پارامترهای SAM باید آخرین بار قرار بگیرند تا بتوانید استفاده از ایدیوماتیک از Kotlin را فعال کنید ، حتی اگر این روش با پارامترهای اضافی بیش از حد بارگیری شود.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
اسناد
اینها قوانینی در مورد اسناد عمومی (Javadoc) برای API ها است.
همه API های عمومی باید مستند شوند
تمام API های عمومی باید مستندات کافی داشته باشند تا توضیح دهند که چگونه یک توسعه دهنده از API استفاده می کند. فرض کنید توسعه دهنده این روش را با استفاده از خودکار یا در حالی که از طریق اسناد مرجع API مرور می کند ، پیدا کرده و از سطح API مجاور حداقل مقدار متن دارد (برای مثال ، همان کلاس).
روش ها
پارامترهای روش و مقادیر بازگشت باید به ترتیب با استفاده از @param
و @return
DOCS ثبت شوند. بدن 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
، راهنمایی های مربوط به حاشیه نویسی را مشاهده کنید.
هنگام افزودن Javadoc ، به روزرسانی-API یا Docs Target را اجرا کنید
این قانون هنگام افزودن برچسب های @link
یا @see
از اهمیت ویژه ای برخوردار است و مطمئن شوید که خروجی مطابق انتظار به نظر می رسد. خروجی خطا در Javadoc اغلب به دلیل پیوندهای بد است. یا update-api
یا docs
باعث می شود Target این بررسی را انجام دهد ، اما اگر فقط Javadoc را تغییر دهید ، هدف docs
سریعتر خواهد بود و در غیر این صورت نیازی به اجرای هدف update-api
ندارید.
برای تشخیص مقادیر جاوا از {code foo} استفاده کنید
مقادیر جاوا مانند true
، false
و null
را با {@code...}
بسته بندی کنید تا آنها را از متن مستندات متمایز کنید.
هنگام نوشتن مستندات در منابع کوتلین ، می توانید کد را با پشتی مانند آنچه را که می خواهید برای Markdown بسته بندی کنید.
خلاصه های param و return باید یک قطعه جمله ای باشد
خلاصه های پارامتر و بازگشت باید با یک شخصیت کوچک شروع شود و فقط یک قطعه جمله ای را شامل شود. اگر اطلاعات اضافی دارید که فراتر از یک جمله واحد است ، آن را به روش Javadoc Method منتقل کنید:
/**
* @param e The element to be appended to the list. This must not be
* null. If the list contains no entries, this element will
* be added at the beginning.
* @return This method returns true on success.
*/
باید به:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
حاشیه نویسی اسناد نیاز به توضیحات دارد
سند چرا حاشیه نویسی @hide
و @removed
از API عمومی پنهان شده اند. دستورالعمل هایی را برای نحوه جایگزینی عناصر API مشخص شده با حاشیه نویسی @deprecated
درج کنید.
از throws برای مستند کردن استثنائات استفاده کنید
اگر یک روش یک استثناء بررسی شده ، به عنوان مثال IOException
را پرتاب می کند ، استثنا را با @throws
مستند کنید. برای API های تأمین شده با Kotlin در نظر گرفته شده توسط مشتری های جاوا ، عملکردهای حاشیه نویسی با @Throws
.
اگر یک روش یک استثناء بدون بررسی را نشان می دهد که نشانگر یک خطای قابل پیشگیری است ، به عنوان مثال IllegalArgumentException
و یا IllegalStateException
، استثنا را با توضیح اینکه چرا استثناء پرتاب می شود ، مستند کنید. استثناء پرتاب شده همچنین باید نشان دهد که چرا پرتاب شده است.
موارد خاصی از استثناء نشده بدون بررسی ضمنی تلقی می شود و نیازی به مستند سازی نیست ، مانند NullPointerException
یا IllegalArgumentException
که در آن یک استدلال @IntDef
یا حاشیه نویسی مشابه که قرارداد API را در امضای روش جاسازی نمی کند ، مطابقت ندارد.
/**
* ...
* @throws IOException If it cannot find the schema for {@code toVersion}
* @throws IllegalStateException If the schema validation fails
*/
public SupportSQLiteDatabase runMigrationsAndValidate(String name, int version,
boolean validateDroppedTables, Migration... migrations) throws IOException {
// ...
if (!dbPath.exists()) {
throw new IllegalStateException("Cannot find the database file for " + name
+ ". Before calling runMigrations, you must first create the database "
+ "using createDatabase.");
}
// ...
یا در کوتلین:
/**
* ...
* @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
ثبت شود ، مگر اینکه در واقع از روش حاشیه نویسی تجدید نظر کنند.
جمله اول اسناد را با یک دوره پایان دهید
The Doclava tool parses docs simplistically, ending the synopsis doc (the first sentence, used in the quick description at the top of the class docs) as soon as it sees a period (.) followed by a space. این باعث دو مشکل می شود:
- If a short doc doesn't end with a period, and if that member has inherited docs that are picked up by the tool, then the synopsis also picks up those inherited docs. For example, see
actionBarTabStyle
in theR.attr
docs , which has the description of the dimension added into the synopsis. - Avoid "eg" in the first sentence for the same reason, because Doclava ends the synopsis docs after "g.". For example, see
TEXT_ALIGNMENT_CENTER
inView.java
. Note that Metalava automatically corrects this error by inserting a nonbreaking space after the period; however, don't make this mistake in the first place.
Format docs to be rendered in HTML
Javadoc is rendered in HTML, so format these docs accordingly:
Line breaks should use an explicit
<p>
tag. Don't add a closing</p>
tag.Don't use ASCII to render lists or tables.
Lists should use
<ul>
or<ol>
for unordered and ordered, respectively. Each item should begin with an<li>
tag, but doesn't need a closing</li>
tag. A closing</ul>
or</ol>
tag is required after the last item.Tables should use
<table>
,<tr>
for rows,<th>
for headers, and<td>
for cells. All table tags require matching closing tags. You can useclass="deprecated"
on any tag to denote deprecation.To create inline code font, use
{@code foo}
.To create code blocks, use
<pre>
.All text inside a
<pre>
block is parsed by the browser, so be careful with brackets<>
. You can escape them with<
and>
HTML entities.Alternatively, you can leave raw brackets
<>
in your code snippet if you wrap the offending sections in{@code foo}
. به عنوان مثال:<pre>{@code <manifest>}</pre>
Follow the API reference style guide
To provide consistency in the style for class summaries, method descriptions, parameter descriptions, and other items, follow the recommendations in the official Java language guidelines at How to Write Doc Comments for the Javadoc Tool .
Android Framework-specific rules
These rules are about APIs, patterns, and data structures that are specific to APIs and behaviors built into the Android framework (for example, Bundle
or Parcelable
).
Intent builders should use the create*Intent() pattern
Creators for intents should use methods named createFooIntent()
.
Use Bundle instead of creating new general-purpose data structures
Avoid creating new general-purpose data structures to represent arbitrary key to typed value mappings. Instead, consider using Bundle
.
This typically comes up when writing platform APIs that serve as communication channels between nonplatform apps and services, where the platform doesn't read the data sent across the channel and the API contract may be partially defined outside of the platform (for example, in a Jetpack library).
In cases where the platform does read the data, avoid using Bundle
and prefer a strongly typed data class.
Parcelable implementations must have public CREATOR field
Parcelable inflation is exposed through CREATOR
, not raw constructors. If a class implements Parcelable
, then its CREATOR
field must also be a public API and the class constructor taking a Parcel
argument must be private.
Use CharSequence for UI strings
When a string is presented in a user interface, use CharSequence
to allow for Spannable
instances.
If it's just a key or some other label or value that isn't visible to users, String
is fine.
Avoid using Enums
IntDef
must be used over enums in all platform APIs, and should be strongly considered in unbundled, library APIs. Use enums only when you're certain that new values won't be added.
Benefits of IntDef
:
- Enables adding values over time
- Kotlin
when
statements can fail at runtime if they become no-longer-exhaustive due to an added enum value in platform.
- Kotlin
- No classes or objects used at runtime, only primitives
- While R8 or minfication can avoid this cost for unbundled library APIs, this optimization can't affect platform API classes.
Benefits of Enum
- Idiomatic language feature of Java, Kotlin
- Enables exhaustive switch,
when
statement usage- Note - values must not change over time, see previous list
- Clearly scoped, and discoverable naming
- Enables compile time verification
- For example, a
when
statement in Kotlin that returns a value
- For example, a
- Is a functioning class that can implement interfaces, have static helpers, expose member or extension methods, and expose fields.
Follow Android package layering hierarchy
The android.*
package hierarchy has an implicit ordering, where lower-level packages can't depend on higher-level packages.
Avoid referring to Google, other companies, and their products
The Android platform is an open-source project and aims to be vendor neutral. The API should be generic and equally usable by system integrators or apps with the requisite permissions.
Parcelable implementations should be final
Parcelable classes defined by the platform are always loaded from framework.jar
, so it is invalid for an app to try overriding a Parcelable
implementation.
If the sending app extends a Parcelable
, the receiving app won't have the sender's custom implementation to unpack with. Note about backward compatibility: if your class historically wasn't final, but didn't have a publicly available constructor, you still can mark it final
.
Methods calling into system process should rethrow RemoteException as RuntimeException
RemoteException
is typically thrown by internal AIDL, and indicates that the system process has died, or the app is trying to send too much data. In both cases, public API should rethrow as a RuntimeException
to prevent apps from persisting security or policy decisions.
If you know the other side of a Binder
call is the system process, this boilerplate code is the best-practice:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Throw specific exceptions for API changes
Public API behaviors might change across API levels and cause app crashes (for instance to enforce new security policies).
When the API needs to throw for a request that was previously valid, throw a new specific exception instead of a generic one. For example, ExportedFlagRequired
instead of SecurityException
(and ExportedFlagRequired
can extend SecurityException
).
This will help app developers and tools detect API behavior changes.
Implement copy constructor instead of clone
Use of the Java clone()
method is strongly discouraged due to the lack of API contracts provided by the Object
class and difficulties inherent in extending classes that use clone()
. Instead, use a copy constructor that takes an object of the same type.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
Classes that rely on a Builder for construction should consider adding a Builder copy constructor to allow modifications to the copy.
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
Use ParcelFileDescriptor over FileDescriptor
The java.io.FileDescriptor
object has a poor definition of ownership, which can result in obscure use-after-close bugs. Instead, APIs should return or accept ParcelFileDescriptor
instances. Legacy code can convert between PFD and FD if needed using dup() or getFileDescriptor() .
Avoid using odd-sized numerical values
Avoid using short
or byte
values directly, because they often limit how you might be able to evolve the API in the future.
Avoid using BitSet
java.util.BitSet
is great for implementation but not for public API. It's mutable, requires an allocation for high-frequency method calls, and doesn't provide semantic meaning for what each bit represents.
For high-performance scenarios, use an int
or long
with @IntDef
. For low-performance scenarios, consider a Set<EnumType>
. For raw binary data, use byte[]
.
Prefer android.net.Uri
android.net.Uri
is the preferred encapsulation for URIs in Android APIs.
Avoid java.net.URI
, because it is overly strict in parsing URIs, and never use java.net.URL
, because its definition of equality is severely broken.
Hide annotations marked as @IntDef, @LongDef, or @StringDef
Annotations marked as @IntDef
, @LongDef
, or @StringDef
denote a set of valid constants that can be passed to an API. However, when they are exported as APIs themselves, the compiler inlines the constants and only the (now useless) values remain in the annotation's API stub (for the platform) or JAR (for libraries).
As such, usages of these annotations must be marked with the @hide
docs annotation in the platform or @RestrictTo.Scope.LIBRARY)
code annotation in libraries. They must be marked @Retention(RetentionPolicy.SOURCE)
in both cases to prevent them from appearing in API stubs or JARs.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
When building the platform SDK and library AARs, a tool extracts the annotations and bundles them separately from the compiled sources. Android Studio reads this bundled format and enforces the type definitions.
Don't add new setting provider keys
Don't expose new keys from Settings.Global
, Settings.System
, or Settings.Secure
.
Instead, add a proper getter and setter Java API in a relevant class, which is typically a "manager" class. Add a listener mechanism or a broadcast to notify clients of changes as needed.
SettingsProvider
settings have a number of problems compared to getters/setters:
- No type safety.
- No unified way to provide a default value.
- No proper way to customize permissions.
- For example, it's not possible to protect your setting with a custom permission.
- No proper way to add custom logic properly.
- For example, it's not possible to change setting A's value depending on setting B's value.
Example: Settings.Secure.LOCATION_MODE
has existed for a long time, but the location team has deprecated it for a proper Java API LocationManager.isLocationEnabled()
and the MODE_CHANGED_ACTION
broadcast, which gave the team a lot more flexibility, and the semantics of the APIs are a lot clearer now.
Don't extend Activity and AsyncTask
AsyncTask
is an implementation detail. Instead, expose a listener or, in androidx, a ListenableFuture
API instead.
Activity
subclasses are impossible to compose. Extending activity for your feature makes it incompatible with other features that require users to do the same. Instead, rely on composition by using tools such as LifecycleObserver .
Use the Context's getUser()
Classes bound to a Context
, such as anything returned from Context.getSystemService()
should use the user bound to the Context
instead of exposing members that target specific users.
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);
}
}
Exception: A method may accept a user argument if it accepts values that don't represent a single user, such as UserHandle.ALL
.
Use UserHandle instead of plain ints
UserHandle
is preferred to provide type safety and avoid conflating user IDs with uids.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Where unavoidable, an int
representing a user ID must be annotated with @UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Prefer listeners or callbacks to broadcast intents
Broadcast intents are very powerful, but they've resulted in emergent behaviors that can negatively impact system health, and so new broadcast intents should be added judiciously.
Here are some specific concerns which result in us discouraging the introduction of new broadcast intents:
When sending broadcasts without the
FLAG_RECEIVER_REGISTERED_ONLY
flag, they force-start any apps that aren't already running. While this can sometimes be an intended outcome, it can result in stampeding of dozens of apps, negatively impacting system health. We'd recommend using alternative strategies, such asJobScheduler
, to better coordinate when various preconditions are met.When sending broadcasts, there is little ability to filter or adjust the content delivered to apps. This makes it difficult or impossible to respond to future privacy concerns, or introduce behavior changes based on the target SDK of the receiving app.
Since broadcast queues are a shared resource, they can become overloaded and may not result in timely delivery of your event. We've observed several broadcast queues in the wild which have an end-to-end latency of 10 minutes or longer.
For these reasons, we encourage new features to consider using listeners or callbacks or other facilities such as JobScheduler
instead of broadcast intents.
In cases where broadcast intents still remain the ideal design, here are some best-practices that should be considered:
- If possible, use
Intent.FLAG_RECEIVER_REGISTERED_ONLY
to limit your broadcast to apps that are already running. For example,ACTION_SCREEN_ON
uses this design to avoid waking up apps. - If possible, use
Intent.setPackage()
orIntent.setComponent()
to target the broadcast at a specific app of interest. For example,ACTION_MEDIA_BUTTON
uses this design to focus on the current app handling playback controls. - If possible, define your broadcast as a
<protected-broadcast>
to prevent malicious apps from impersonating the OS.
Intents in system-bound developer services
Services that are intended to be extended by the developer and bound by the system, for example abstract services like NotificationListenerService
, may respond to an Intent
action from the system. Such services should meet the following criteria:
- Define a
SERVICE_INTERFACE
string constant on the class containing the fully-qualified class name of the service. This constant must be annotated with@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Document on the class that a developer must add an
<intent-filter>
to theirAndroidManifest.xml
in order to receive Intents from the platform. - Strongly consider adding a system-level permission to prevent rogue apps from sending
Intent
s to developer services.
Kotlin-Java interop
See the official Android Kotlin-Java interop guide for a full list of guidelines. Select guidelines have been copied to this guide to improve discoverability.
API visibility
Some Kotlin APIs, like suspend fun
s, aren't intended to be used by Java developers; however, don't attempt to control language-specific visibility using @JvmSynthetic
as it has side-effects on how the API is presented in debuggers that make debugging more difficult.
See the Kotlin-Java interop guide or Async guide for specific guidance.
اشیاء همراه
Kotlin uses companion object
to expose static members. In some cases, these will show up from Java on an inner class named Companion
rather than on the containing class. Companion
classes may show as empty classes in API text files -- that is working as intended.
To maximize compatibility with Java, annotate companion objects' non-constant fields with @JvmField
and public functions with @JvmStatic
to expose them directly on the containing class.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Evolution of Android platform APIs
This section describes policies regarding what types of changes you can make to existing Android APIs and how you should implement those changes to maximize compatibility with existing apps and codebases.
Binary-breaking changes
Avoid binary-breaking changes in finalized public API surfaces. These types of changes generally raise errors when running make update-api
, but there might be edge cases that Metalava's API check doesn't catch. When in doubt, refer to the Eclipse Foundation's Evolving Java-based APIs guide for a detailed explanation of what types of API changes are compatible in Java. Binary-breaking changes in hidden (for example, system) APIs should follow the deprecate/replace cycle.
Source-breaking changes
We discourage source-breaking changes even if they aren't binary breaking. One example of a binary compatible but source-breaking change is adding a generic to an existing class, which is binary compatible but can introduce compilation errors due to inheritance or ambiguous references. Source-breaking changes won't raise errors when running make update-api
, so you must take care to understand the impact of changes to existing API signatures.
In some cases, source-breaking changes become necessary to improve the developer experience or code correctness. For example, adding nullability annotations to Java sources improves interoperability with Kotlin code and reduces the likelihood of errors, but often requires changes -- sometimes significant changes -- to source code.
Changes to private APIs
You can change APIs annotated with @TestApi
at any time.
You must preserve APIs annotated with @SystemApi
for three years. You must remove or refactor a system API on the following schedule:
- API y - Added
- API y+1 - Deprecation
- Mark the code with
@Deprecated
. - Add replacements, and link to the replacement in the Javadoc for the deprecated code using the
@deprecated
docs annotation. - During the development cycle, file bugs against internal users telling them the API is being deprecated. This helps validate that the replacement APIs are adequate.
- Mark the code with
- API y+2 - Soft removal
- Mark the code with
@removed
. - Optionally, throw or no-op for apps that target the current SDK level for the release.
- Mark the code with
- API y+3 - Hard removal
- Completely remove the code from the source tree.
منسوخ شدن
We consider deprecation an API change, and it can occur in a major (such as letter) release. Use the @Deprecated
source annotation and @deprecated <summary>
docs annotation together when deprecating APIs. Your summary must include a migration strategy. This strategy might link to a replacement API or explain why you shouldn't use the API:
/**
* Simple version of ...
*
* @deprecated Use the {@link androidx.fragment.app.DialogFragment}
* class with {@link androidx.fragment.app.FragmentManager}
* instead.
*/
@Deprecated
public final void showDialog(int id)
You must also deprecate APIs defined in XML and exposed in Java, including attributes and styleable properties exposed in the android.R
class, with a summary:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
When to deprecate an API
Deprecations are most useful for discouraging adoption of an API in new code .
We also require that you mark APIs as @deprecated
before they're @removed
, but this doesn't provide strong motivation for developers to migrate away from an API they're already using.
Before deprecating an API, consider the impact on developers. The effects of deprecating an API include:
-
javac
emits a warning during compilation.- Deprecation warnings can't be suppressed globally or baselined, so developers using
-Werror
need to individually fix or suppress every usage of a deprecated API before they can update their compile SDK version. - Deprecation warnings on imports of deprecated classes can't be suppressed. As a result, developers need to inline the fully qualified class name for every usage of a deprecated class before they can update their compile SDK version.
- Deprecation warnings can't be suppressed globally or baselined, so developers using
- Documentation on
d.android.com
shows a deprecation notice. - IDEs like Android Studio show a warning at the API usage site.
- IDEs might down-rank or hide the API from auto-complete.
As a result, deprecating an API can discourage the developers who are the most concerned about code health (those using -Werror
) from adopting new SDKs. Developers who aren't concerned about warnings in their existing code are likely to ignore deprecations altogether.
An SDK that introduces a large number of deprecations makes both of these cases worse.
For this reason, we recommend deprecating APIs only in cases where:
- We plan to
@remove
the API in a future release. - API use leads to incorrect or undefined behavior that we can't fix without breaking compatibility.
When you deprecate an API and replace it with a new API, we strongly recommend adding a corresponding compatibility API to a Jetpack library like androidx.core
to simplify supporting both old and new devices.
We don't recommend deprecating APIs that work as intended in current and future releases:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
Deprecation is appropriate in cases where APIs can no longer maintain their documented behaviors:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Changes to deprecated APIs
You must maintain the behavior of deprecated APIs. This means test implementations must remain the same, and tests must continue to pass after you have deprecated the API. If the API doesn't have tests, you should add tests.
Don't expand deprecated API surfaces in future releases. You can add lint correctness annotations (for example, @Nullable
) to an existing deprecated API, but shouldn't add new APIs.
Don't add new APIs as deprecated. If any APIs were added and subsequently deprecated within a prerelease cycle (thus would initially enter the public API surface as deprecated), you must remove them before finalizing the API.
Soft removal
Soft removal is a source-breaking change, and you should avoid it in public APIs unless the API Council explicitly approves it. For system APIs, you must deprecate the API for the duration of a major release before a soft removal. Remove all docs references to the APIs and use the @removed <summary>
docs annotation when soft-removing APIs. Your summary must include the reason for removal and can include a migration strategy, as we explained in Deprecation .
The behavior of soft-removed APIs can be maintained as is, but more importantly must be preserved such that existing callers won't crash when calling the API. In some cases, that might mean preserving behavior.
Test coverage must be maintained, but the content of the tests might need to change to accommodate for behavioral changes. Tests must still validate that existing callers don't crash at run time. You can maintain the behavior of soft-removed APIs as is, but more importantly, you must preserve it such that existing callers won't crash when calling the API. In some cases, that might mean preserving behavior.
You must maintain test coverage, but the content of the tests might need to change to accommodate behavioral changes. Tests must still validate that existing callers don't crash at run time.
At a technical level, we remove the API from the SDK stub JAR and compile-time classpath using the @remove
Javadoc annotation, but it still exists on the run-time classpath -- similar to @hide
APIs:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
From an app developer perspective, the API no longer appears in auto-complete and source code that references the API won't compile when the compileSdk
is equal to or later than the SDK at which the API was removed; however, source code continues to compile successfully against earlier SDKs and binaries that reference the API continue to work.
Certain categories of API must not be soft removed. You must not soft remove certain categories of API.
روش های انتزاعی
You must not soft remove abstract methods on classes that developers might extend. Doing so makes it impossible for developers to successfully extend the class across all SDK levels.
In rare cases where it was never and won't be possible for developers to extend a class, you can still soft remove abstract methods.
Hard removal
Hard removal is a binary-breaking change and should never occur in public APIs.
Discouraged annotation
We use the @Discouraged
annotation to indicate that we don't recommend an API in most (>95%) cases. Discouraged APIs differ from deprecated APIs in that there exists a narrow critical use case that prevents deprecation. When you mark an API as discouraged, you must provide an explanation and an alternative solution:
@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);
}
You shouldn't add new APIs as discouraged.
Changes to the behavior of existing APIs
In some cases, you might want to change the implementation behavior of an existing API. For example, in Android 7.0 we improved DropBoxManager
to clearly communicate when developers tried posting events that were too large to send across Binder
.
However, to avoid causing problems for existing apps, we strongly recommend preserving a safe behavior for older apps. We've historically guarded these behavior changes based on the ApplicationInfo.targetSdkVersion
of the app, but we've recently migrated to require using the App Compatibility Framework. Here's an example of how to implement a behavior change using this new framework:
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
public class MyClass {
@ChangeId
// This means the change will be enabled for target SDK R and higher.
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
// Use a bug number as the value, provide extra detail in the bug.
// FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
static final long FOO_NOW_DOES_X = 123456789L;
public void doFoo() {
if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
// do the new thing
} else {
// do the old thing
}
}
}
Using this App Compatibility Framework design enables developers to temporarily disable specific behavior changes during preview and beta releases as part of debugging their apps, instead of forcing them to adjust to dozens of behavior changes simultaneously.
سازگاری رو به جلو
Forward compatibility is a design characteristic that allows a system to accept input intended for a later version of itself. In the case of API design, you must pay special attention to the initial design as well as future changes because developers expect to write code once, test it once, and have it run everywhere without issue.
The following cause the most common forward-compatibility issues in Android:
- Adding new constants to a set (such as
@IntDef
orenum
) previously assumed to be complete (for example, whereswitch
has adefault
that throws an exception). - Adding support for a feature that isn't captured directly in the API surface (for example, support for assigning
ColorStateList
-type resources in XML where previously only<color>
resources were supported). - Loosening restrictions on run-time checks, for example removing a
requireNotNull()
check that was present on lower versions.
In all of these cases, developers find out that something is wrong only at run time. Worse, they might find out as a result of crash reports from older devices in the field.
Additionally, these cases are all technically valid API changes. They don't break binary or source compatibility and API lint won't catch any of these issues.
As a result, API designers must pay careful attention when modifying existing classes. Ask the question, "Is this change going to cause code that's written and tested only against the latest version of the platform to fail on lower versions?"
XML schemas
If an XML schema serves as a stable interface between components, that schema must be explicitly specified and must evolve in a backward-compatible manner, similar to other Android APIs. For example, the structure of XML elements and attributes must be preserved similar to how methods and variables are maintained on other Android API surfaces.
XML deprecation
If you'd like to deprecate an XML element or attribute, you can add the xs:annotation
marker, but you must continue to support any existing XML files by following the typical @SystemApi
evolution lifecycle.
<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>
Element types must be preserved
Schemas support the sequence
element, choice
element and all
elements as child elements of complexType
element. However, these child elements differ in the number and order of their child elements, so modifying an existing type would be an incompatible change.
If you want to modify an existing type, the best-practice is to deprecate the old type and introduce a new type to replace it.
<!-- 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-specific patterns
Mainline is a project to allow updating subsystems ("mainline modules") of the Android OS individually, rather than updating the whole system image.
Mainline modules have to be "unbundled" from the core platform, which means all the interactions between each module and the rest of the world have to be done using formal (public or system) APIs.
There are certain design patterns mainline modules should follow. This section describes them.
The <Module>FrameworkInitializer pattern
If a mainline module needs to exposes @SystemService
classes (for example, JobScheduler
) then use the following pattern:
Expose a
<YourModule>FrameworkInitializer
class from your module. This class needs to be in$BOOTCLASSPATH
. Example: StatsFrameworkInitializerMark it with
@SystemApi(client = MODULE_LIBRARIES)
.Add a
public static void registerServiceWrappers()
method to it.Use
SystemServiceRegistry.registerContextAwareService()
to register a service manager class when it needs a reference to aContext
.Use
SystemServiceRegistry.registerStaticService()
to register a service manager class when it doesn't need a reference to aContext
.Call the
registerServiceWrappers()
method fromSystemServiceRegistry
's static initializer.
The <Module>ServiceManager pattern
Normally, in order to register system service binder objects or get references to them, one would use ServiceManager
, but mainline modules can't use it because it's hidden. This class is hidden because mainline modules aren't supposed to register or refer to system service binder objects exposed by the static platform or by other modules.
Mainline modules can use the following pattern instead to be able to register and get references to binder services that are implemented inside the module.
Create a
<YourModule>ServiceManager
class, following the design of TelephonyServiceManagerExpose the class as
@SystemApi
. If you only need to access it from$BOOTCLASSPATH
classes or system server classes, you can use@SystemApi(client = MODULE_LIBRARIES)
; otherwise@SystemApi(client = PRIVILEGED_APPS)
would work.This class would consists of:
- A hidden constructor, so only the static platform code can instantiate it.
- Public getter methods that return a
ServiceRegisterer
instance for a specific name. If you have one binder object, then you need one getter method. If you have two, then you need two getters. - In
ActivityThread.initializeMainlineModules()
, instantiate this class, and pass it to a static method exposed by your module. Normally, you add a static@SystemApi(client = MODULE_LIBRARIES)
API in yourFrameworkInitializer
class that takes it.
This pattern would prevent other mainline modules from accessing these APIs because there's no way for other modules to get an instance of <YourModule>ServiceManager
, even though the get()
and register()
APIs are visible to them.
Here is how telephony gets a reference to the telephony service: code search link .
If your implements a service binder object in native code, you use the AServiceManager
native APIs . These APIs correspond to the ServiceManager
Java APIs but the native ones are directly exposed to mainline modules. Don't use them to register or refer to binder objects that aren't owned by your module. If you expose a binder object from native, your <YourModule>ServiceManager.ServiceRegisterer
doesn't need a register()
method.
Permission definitions in Mainline modules
Mainline modules containing APKs may define (custom) permissions in their APK AndroidManifest.xml
in the same way as a regular APK.
If the defined permission is only used internally within a module, its permission name should be prefixed with the APK package name, for example:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
If the defined permission is to be provided as part of an updatable platform API to other apps, its permission name should be prefixed with "android.permission." (like any static platform permission) plus the module package name, to signal it's a platform API from a module while avoiding any naming conflicts, for example:
<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" />
Then the module can expose this permission name as an API constant in its API surface, for example HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.
This page is intended to be a guide for developers to understand the general principles that the API Council enforces in API reviews.
In addition to following these guidelines when writing APIs, developers should run the API Lint tool, which encodes many of these rules in checks that it runs against APIs.
Think of this as the guide to the rules that are obeyed by that Lint tool, plus general advice on rules that can't be codified into that tool with high accuracy.
ابزار API Lint
API Lint is integrated into the Metalava static analysis tool and runs automatically during validation in CI. You can run it manually from a local platform checkout using m checkapi
or a local AndroidX checkout using ./gradlew :path:to:project:checkApi
.
قوانین API
The Android platform and many Jetpack libraries existed before this set of guidelines was created, and the policies set forth later in this page are continually evolving to meet the needs of the Android ecosystem.
As a result, some existing APIs may not follow the guidelines. In other cases, it might provide a better user experience for app developers if a new API stays consistent with existing APIs rather than strictly adhere to the guidelines.
Use your judgement and reach out to API Council if there are difficult questions about an API that need to be resolved or guidelines that need to be updated.
اصول API
This category pertains to the core aspects of an Android API.
All APIs must be implemented
Irrespective of an API's audience (for example, public or @SystemApi
), all API surfaces must be implemented when merged or exposed as API. Don't merge API stubs with implementation to come at a later date.
API surfaces without implementations have multiple issues:
- There is no guarantee that a proper or complete surface has been exposed. Until an API is tested or used by clients, there is no way to verify a client has the appropriate APIs to be able to use the feature.
- APIs without implementation can't be tested in Developer Previews.
- APIs without implementation can't be tested in CTS.
All APIs must be tested
This is in line with platform CTS requirements, AndroidX policies, and generally the idea that APIs must be implemented.
Testing API surfaces provides a base guarantee that the API surface is usable and we have addressed expected use cases. Testing for existence isn't sufficient; the behavior of the API itself must be tested.
A change that adds a new API should include corresponding tests in the same CL or Gerrit topic.
APIs should also be testable . You should be able to answer the question, "How will an app developer test code that uses your API?"
All APIs must be documented
Documentation is a key part of API usability. While the syntax of an API surface may seem obvious, any new clients won't understand the semantics, behavior, or context behind the API.
All generated APIs must be compliant with the guidelines
APIs generated by tools must follow the same API guidelines as hand-written code.
Tools that are discouraged for generating APIs:
-
AutoValue
: violates guidelines in various ways, for example, there is no way to implement final value classes nor final builders with the way AutoValue works.
سبک کد
This category pertains to the general code style that developers should use, especially when writing public APIs.
به جز موارد ذکر شده، از قراردادهای استاندارد کدگذاری پیروی کنید
Android coding conventions are documented for external contributors here:
https://source.android.com/source/code-style.html
Overall, we tend to follow standard Java and Kotlin coding conventions.
Acronyms shouldn't be capitalized in method names
For example: method name should be runCtsTests
and not runCTSTests
.
Names shouldn't end with Impl
This exposes implementation details, avoid that.
کلاس ها
This section describes rules about classes, interfaces, and inheritance.
Inherit new public classes from the appropriate base class
Inheritance exposes API elements in your subclass that might not be appropriate. For example, a new public subclass of FrameLayout
looks like FrameLayout
plus the new behaviors and API elements. If that inherited API isn't appropriate for your use case, inherit from a class further up the tree, for example, ViewGroup
or View
.
If you're tempted to override methods from the base class to throw UnsupportedOperationException
, reconsider which base class you're using.
Use the base collections classes
Whether taking a collection as an argument or returning it as a value, always prefer the base class over the specific implementation (such as return List<Foo>
rather than ArrayList<Foo>
).
Use a base class that expresses appropriate constraints for the API. For example, use List
for an API whose collection must be ordered, and use Set
for an API whose collection must consist of unique elements.
In Kotlin, prefer immutable collections. See Collection mutability for more details.
Abstract classes versus interfaces
Java 8 adds support for default interface methods, which allows API designers to add methods to interfaces while maintaining binary compatibility. Platform code and all Jetpack libraries should target Java 8 or later.
In cases where the default implementation is stateless, API designers should prefer interfaces over abstract classes -- that is, default interface methods can be implemented as calls to other interface methods.
In cases where a constructor or internal state is required by the default implementation, abstract classes must be used.
In both cases, API designers can choose to leave a single method abstract to simplify usage as a 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) { }
}
Class names should reflect what they extend
For example, classes that extend Service
should be named FooService
for clarity:
public class IntentHelper extends Service {}
public class IntentService extends Service {}
پسوندهای عمومی
Avoid using generic class name suffixes like Helper
and Util
for collections of utility methods. Instead, put the methods directly in the associated classes or into Kotlin extension functions.
In cases where methods are bridging multiple classes, give the containing class a meaningful name that explains what it does.
In very limited cases, using the Helper
suffix might be appropriate:
- Used for composition of default behavior
- Might involve delegation of existing behavior to new classes
- Might require persisted state
- Typically involves
View
For example, if backporting tooltips requires persisting the state associated with a View
and calling several methods on the View
to install the backport, TooltipHelper
would be an acceptable class name.
کدهای تولید شده توسط IDL را مستقیماً به عنوان APIهای عمومی در معرض نمایش قرار ندهید
Keep IDL-generated code as implementation details. This includes protobuf, sockets, FlatBuffers, or any other non-Java, non-NDK API surface. However, most IDL in Android is in AIDL, so this page focuses on AIDL.
Generated AIDL classes don't meet the API style guide requirements (for example, they can't use overloading) and the AIDL tool isn't explicitly designed to maintain language API compatibility, so you can't embed them in a public API.
Instead, add a public API layer on top of the AIDL interface, even if it initially is a shallow wrapper.
رابط های بایندر
If the Binder
interface is an implementation detail, it can be changed freely in the future, with the public layer allowing for the required backward compatibility to be maintained. For example, you might need to add new arguments to the internal calls, or optimize IPC traffic by using batching or streaming, using shared memory, or similar. None of these can be done if your AIDL interface is also the public API.
For example, don't expose FooService
as a public API directly:
// BAD: Public API generated from IFooService.aidl
public class IFooService {
public void doFoo(String foo);
}
Instead, wrap the Binder
interface inside a manager or other class:
/**
* @hide
*/
public class IFooService {
public void doFoo(String foo);
}
public IFooManager {
public void doFoo(String foo) {
mFooService.doFoo(foo);
}
}
If later a new argument is needed for this call, the internal interface can be minimal and convenient overloads added to the public API. You can use the wrapping layer to handle other backward-compatibility concerns as the implementation evolves:
/**
* @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);
}
}
For Binder
interfaces that aren't part of the Android platform (for example, a service interface exported by Google Play services for apps to use), the requirement for a stable, published, and versioned IPC interface means that it's much harder to evolve the interface itself. However, it's still worthwhile to have a wrapper layer around it, to match other API guidelines and to make it easier to use the same public API for a new version of the IPC interface, if that ever becomes necessary.
Don't use raw Binder objects in public API
A Binder
object doesn't have any meaning on its own and thus shouldn't be used in public API. One common use case is to use a Binder
or IBinder
as a token because it has identity semantics. Instead of using a raw Binder
object use a wrapper token class instead.
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() {...}
}
کلاس های مدیر باید نهایی باشد
Manager classes should be declared as final
. Manager classes talk to system services and are the single point of interaction. There is no need for customization so declare it as final
.
Don't use CompletableFuture or Future
java.util.concurrent.CompletableFuture
has a large API surface that permits arbitrary mutation of the future's value and has error-prone defaults .
Conversely, java.util.concurrent.Future
is missing nonblocking listening, making it hard to use with asynchronous code.
In platform code and low-level library APIs consumed by both Kotlin and Java , prefer a combination of a completion callback, Executor
, and if the API supports cancellation CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
Executor callbackExecutor,
android.os.OutcomeReceiver<FooResult, Throwable> callback);
If you're targeting Kotlin , prefer suspend
functions.
suspend fun asyncLoadFoo(): Foo
In Java-specific integration libraries , you can use Guava's ListenableFuture
.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
Don't use Optional
While Optional
can have advantages in some API surfaces, it's inconsistent with the existing Android API surface area. @Nullable
and @NonNull
provide tooling assistance for null
safety and Kotlin enforces nullability contracts at the compiler level, making Optional
unnecessary.
For optional primitives, use paired has
and get
methods. If the value isn't set ( has
returns false
), the get
method should throw an IllegalStateException
.
public boolean hasAzimuth() { ... }
public int getAzimuth() {
if (!hasAzimuth()) {
throw new IllegalStateException("azimuth is not set");
}
return azimuth;
}
Use private constructors for noninstantiable classes
Classes that can only be created by Builder
s, classes containing only constants or static methods, or otherwise noninstantiable classes should include at least one private constructor to prevent instantiation using the default no-arg constructor.
public final class Log {
// Not instantiable.
private Log() {}
}
تک تن ها
Singleton are discouraged because they have the following testing-related drawbacks:
- Construction is managed by the class, preventing the use of fakes
- Tests can't be hermetic due to the static nature of a singleton
- To work around these issues, developers either have to know the internal details of the singleton or create a wrapper around it
Prefer the single instance pattern, which relies on an abstract base class to address these issues.
Single instance
Single instance classes use an abstract base class with a private
or internal
constructor and provide a static getInstance()
method to obtain an instance. The getInstance()
method must return the same object on subsequent calls.
The object returned by getInstance()
should be a private implementation of the abstract base class.
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
}
}
}
Single instance differs from singleton in that developers can create a fake version of SingleInstance
and use their own Dependency Injection framework to manage the implementation without having to create a wrapper, or the library can provide its own fake in a -testing
artifact.
Classes that release resources should implement AutoCloseable
Classes that release resources through close
, release
, destroy
or similar methods should implement java.lang.AutoCloseable
to allow developers to automatically clean up these resources when using a try-with-resources
block.
از معرفی زیر کلاسهای View جدید در اندروید خودداری کنید.*
Don't introduce new classes that inherit directly or indirectly from android.view.View
in the platform public API (that is, in android.*
).
Android's UI toolkit is now Compose-first . New UI features exposed by the platform should be exposed as lower-level APIs that can be used to implement Jetpack Compose and optionally View-based UI components for developers in Jetpack libraries. Offering these components in libraries affords opportunities for backported implementations when platform features are not available.
فیلدها
These rules are about public fields on classes.
زمینه های خام را در معرض دید قرار ندهید
Java classes shouldn't expose fields directly. Fields should be private and accessible only using public getters and setters regardless of whether these fields are final or not.
Rare exceptions include basic data structures where there is no need to enhance behavior of specifying or retrieving a field. In such cases, the fields should be named using standard variable naming conventions, for example, Point.x
and Point.y
.
Kotlin classes can expose properties.
Exposed fields should be marked final
Raw fields are strongly discouraged (@see Don't expose raw fields ). But in the rare situation where a field is exposed as a public field, mark that field final
.
زمینه های داخلی نباید در معرض دید قرار گیرند
Don't reference internal field names in public API.
public int mFlags;
Use public instead of protected
@see Use public instead of protected
ثابت ها
These are rules about public constants.
Flag constants shouldn't overlap int or long values
Flags implies bits that can be combined into some union value. If this isn't the case, don't call the variable or constant 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;
See @IntDef
for bitmask flags for more information on defining public flag constants.
static final constants should use all-cap, underscore-separated naming convention
All words in the constant should be capitalized and multiple words should be separated by _
. به عنوان مثال:
public static final int fooThing = 5
public static final int FOO_THING = 5
Use standard prefixes for constants
Many of the constants used in Android are for standard things, such as flags, keys, and actions. These constants should have standard prefixes to make them more identifiable as these things.
For example, intent extras should start with EXTRA_
. Intent actions should start with ACTION_
. Constants used with Context.bindService()
should start with BIND_
.
Key constant names and scopes
String constant values should be consistent with the constant name itself, and should generally be scoped to the package or domain. به عنوان مثال:
public static final String FOO_THING = "foo"
is neither named consistently nor appropriately scoped. Instead, consider:
public static final String FOO_THING = "android.fooservice.FOO_THING"
Prefixes of android
in scoped string constants are reserved for the Android Open Source Project.
Intent actions and extras, as well as Bundle entries, should be namespaced using the package name they are defined within.
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"
}
Use public instead of protected
@see Use public instead of protected
از پیشوندهای ثابت استفاده کنید
Related constants should all start with the same prefix. For example, for a set of constants to use with flag values:
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 Use standard prefixes for constants
از نام منابع ثابت استفاده کنید
Public identifiers, attributes, and values must be named using the camelCase naming convention, for example @id/accessibilityActionPageUp
or @attr/textAppearance
, similar to public fields in Java.
In some cases, a public identifier or attribute includes a common prefix separated by an underscore:
- Platform config values such as
@string/config_recentsComponentName
in config.xml - Layout-specific view attributes such as
@attr/layout_marginStart
in attrs.xml
Public themes and styles must follow the hierarchical PascalCase naming convention, for example @style/Theme.Material.Light.DarkActionBar
or @style/Widget.Material.SearchView.ActionBar
, similar to nested classes in Java.
Layout and drawable resources shouldn't be exposed as public APIs. If they must be exposed, however, then public layouts and drawables must be named using the under_score naming convention, for example layout/simple_list_item_1.xml
or drawable/title_bar_tall.xml
.
When constants could change, make them dynamic
The compiler might inline constant values, so keeping values the same is considered part of the API contract. If the value of a MIN_FOO
or MAX_FOO
constant could change in the future, consider making them dynamic methods instead.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Consider forward compatibility for callbacks
Constants defined in future API versions aren't known to apps that target older APIs. For this reason, constants delivered to apps should take into consideration that app's target API version and map newer constants to a consistent value. سناریوی زیر را در نظر بگیرید:
Hypothetical SDK source:
// 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;
Hypothetical app with targetSdkVersion="22"
:
if (result == STATUS_FAILURE) {
// Oh no!
} else {
// Success!
}
In this case, the app was designed within the constraints of API level 22 and made a (somewhat) reasonable assumption that there were only two possible states. If the app receives the newly added STATUS_FAILURE_RETRY
, however, it interprets this as success.
Methods that return constants can safely handle cases like this by constraining their output to match the API level targeted by the app:
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;
}
Developers can't anticipate whether a list of constants might change in the future. If you define an API with an UNKNOWN
or UNSPECIFIED
constant that looks like a catch-all, developers assume that the published constants when they wrote their app are exhaustive. If you're unwilling to set this expectation, reconsider whether a catch-all constant is a good idea for your API.
Additionally, libraries can't specify their own targetSdkVersion
separate from the app and handling targetSdkVersion
behavior changes from library code is complicated and error prone.
Integer or string constant
Use integer constants and @IntDef
if the namespace for values isn't extensible outside of your package. Use string constants if the namespace is shared or can be extended by code outside of your package.
کلاس های داده
Data classes represent a set of immutable properties and provide a small and well-defined set of utility functions for interacting with that data.
Don't use data class
in public Kotlin APIs, as the Kotlin compiler doesn't guarantee language API or binary compatibility for generated code. Instead, manually implement the required functions.
نمونه سازی
In Java, data classes should provide a constructor when there are few properties or use the Builder
pattern when there are many properties.
In Kotlin, data classes should provide a constructor with default arguments regardless of the number of properties. Data classes defined in Kotlin might also benefit from providing a builder when targeting Java clients.
اصلاح و کپی
In cases where data needs to be modified, provide either a Builder
class with a copy constructor (Java) or a copy()
member function (Kotlin) that returns a new object.
When providing a copy()
function in Kotlin, arguments must match the class's constructor and defaults must be populated using the object's current values:
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
)
}
Additional behaviors
Data classes should implement both equals()
and hashCode()
, and every property must be accounted for in the implementations of these methods.
Data classes can implement toString()
with a recommended format matching Kotlin's data class implementation, for example User(var1=Alex, var2=42)
.
روش ها
These are rules about various specifics in methods, around parameters, method names, return types, and access specifiers.
زمان
These rules cover how time concepts like dates and duration should be expressed in APIs.
Prefer java.time.* types where possible
java.time.Duration
, java.time.Instant
and many other java.time.*
types are available on all platform versions through desugaring and should be preferred when expressing time in API parameters or return values.
Prefer exposing only variants of an API that accept or return java.time.Duration
or java.time.Instant
and omit primitive variants with the same use case unless the API domain is one where object allocation in intended usage patterns would have a prohibitive performance impact.
Methods expressing durations should be named duration
If a time value expresses the duration of time involved, name the parameter "duration", not "time".
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
استثنائات:
"timeout" is appropriate when the duration specifically applies to a timeout value.
"time" with a type of java.time.Instant
is appropriate when referring to a specific point in time, not a duration.
Methods expressing durations or time as a primitive should be named with their time unit, and use long
Methods accepting or returning durations as a primitive should suffix the method name with the associated time units (such as Millis
, Nanos
, Seconds
) to reserve the undecorated name for use with java.time.Duration
. زمان را ببینید.
Methods should also be annotated appropriately with their unit and time base:
-
@CurrentTimeMillisLong
: Value is a nonnegative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z. -
@CurrentTimeSecondsLong
: Value is a nonnegative timestamp measured as the number of seconds since 1970-01-01T00:00:00Z. -
@DurationMillisLong
: Value is a nonnegative duration in milliseconds. -
@ElapsedRealtimeLong
: Value is a nonnegative timestamp in theSystemClock.elapsedRealtime()
time base. -
@UptimeMillisLong
: Value is a nonnegative timestamp in theSystemClock.uptimeMillis()
time base.
Primitive time parameters or return values should use long
, not int
.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
Methods expressing units of time should prefer nonabbreviated shorthand for unit names
public void setIntervalNs(long intervalNs);
public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);
public void setTimeoutMicros(long timeoutMicros);
استدلال های طولانی مدت را حاشیه نویسی کنید
The platform includes several annotations to provide stronger typing for long
-type time units:
-
@CurrentTimeMillisLong
: Value is a nonnegative timestamp measured as the number of milliseconds since1970-01-01T00:00:00Z
, thus in theSystem.currentTimeMillis()
time base. -
@CurrentTimeSecondsLong
: Value is a nonnegative timestamp measured as the number of seconds since1970-01-01T00:00:00Z
. -
@DurationMillisLong
: Value is a nonnegative duration in milliseconds. -
@ElapsedRealtimeLong
: Value is a nonnegative timestamp in theSystemClock#elapsedRealtime()
time base. -
@UptimeMillisLong
: Value is a nonnegative timestamp in theSystemClock#uptimeMillis()
time base.
واحدهای اندازه گیری
For all methods expressing a unit of measurement other than time, prefer CamelCased SI unit prefixes .
public long[] getFrequenciesKhz();
public float getStreamVolumeDb();
Put optional parameters at end of overloads
If you have overloads of a method with optional parameters, keep those parameters at the end and keep consistent ordering with the other parameters:
public int doFoo(boolean flag);
public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);
public int doFoo(boolean flag, int id);
When adding overloads for optional arguments, the behavior of the simpler methods should behave in exactly the same way as if default arguments had been provided to the more elaborate methods.
Corollary: Don't overload methods other than to add optional arguments or to accept different types of arguments if the method is polymorphic. If the overloaded method does something fundamentally different, then give it a new name.
روشهای دارای پارامترهای پیشفرض باید با @JvmOverloads حاشیهنویسی شوند (فقط Kotlin)
Methods and constructors with default parameters must be annotated with @JvmOverloads
to maintain binary compatibility.
See Function overloads for defaults in the official Kotlin-Java interop guide for more details.
class Greeting @JvmOverloads constructor(
loudness: Int = 5
) {
@JvmOverloads
fun sayHello(prefix: String = "Dr.", name: String) = // ...
}
مقادیر پارامترهای پیش فرض را حذف نکنید (فقط Kotlin)
If a method has shipped with a parameter with a default value, removal of the default value is a source-breaking change.
متمایزترین و مشخص ترین پارامترهای روش باید ابتدا باشند
If you have a method with multiple parameters, put the most relevant ones first. Parameters that specify flags and other options are less important than those that describe the object that is being acted upon. If there is a completion callback, put it last.
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);
See also: Put optional parameters at end in overloads
سازندگان
The Builder pattern is recommended for creating complex Java objects, and is commonly used in Android for cases where:
- The resulting object's properties should be immutable
- There are a large number of required properties, for example many constructor arguments
- There is a complex relationship between properties at construction time, for example a verification step is required. Note that this level of complexity often indicates problems with the API's usability.
Consider whether you need a builder. Builders are useful in an API surface if they are used to:
- Configure only a few of a potentially large set of optional creation parameters
- Configure many different optional or required creation parameters, sometimes of similar or matching types, where call sites could otherwise become confusing to read or error-prone to write
- Configure the creation of an object incrementally, where several different pieces of configuration code might each make calls on the builder as implementation details
- Allow a type to grow by adding additional optional creation parameters in future API versions
If you have a type with three or fewer required parameters and no optional parameters you can almost always skip a builder and use a plain constructor.
Kotlin-sourced classes should prefer @JvmOverloads
-annotated constructors with default arguments over Builders, but may choose to improve usability for Java clients by also providing Builders in the cases outlined earlier.
class Tone @JvmOverloads constructor(
val duration: Long = 1000,
val frequency: Int = 2600,
val dtmfConfigs: List<DtmfConfig> = emptyList()
) {
class Builder {
// ...
}
}
Builder classes must return the builder
Builder classes must enable method chaining by returning the Builder object (such as this
) from every method except build()
. Additional built objects should be passed as arguments -- don't return a different object's builder. به عنوان مثال:
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();
}
}
In rare cases where a base builder class must support extension, use a generic return type:
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 classes must be created through a constructor
To maintain consistent builder creation through Android API surface, all the builders must be created through a constructor and not a static creator method. For Kotlin-based APIs, the Builder
must be public even if Kotlin users are expected to implicitly rely on the builder through a factory method/DSL style creation mechanism. Libraries must not use @PublishedApi internal
to selectively hide the Builder
class constructor from Kotlin clients.
public class Tone {
public static Builder builder();
public static class Builder {
}
}
public class Tone {
public static class Builder {
public Builder();
}
}
همه آرگومانهای سازنده سازنده باید مورد نیاز باشند (مانند @NonNull)
Optional, for example @Nullable
, arguments should be moved to setter methods. The builder constructor should throw an NullPointerException
(consider using Objects.requireNonNull
) if any required arguments aren't specified.
کلاس های سازنده باید کلاس های داخلی استاتیک نهایی از انواع ساخته شده خود باشند
For the sake of logical organization within a package, builder classes should typically be exposed as final inner classes of their built types, for example Tone.Builder
rather than ToneBuilder
.
Builders may include a constructor to create a new instance from an existing instance
Builders may include a copy constructor to create a new builder instance from an existing builder or built object. They shouldn't provide alternative methods for creating builder instances from existing builders or build objects.
public class Tone {
public static class Builder {
public Builder clone();
}
public Builder toBuilder();
}
public class Tone {
public static class Builder {
public Builder(Builder original);
public Builder(Tone original);
}
}
Builder setters should take @Nullable arguments if the builder has copy constructor
Resetting is essential if a new instance of a builder may be created from an existing instance. If no copy constructor is available, then the builder may have either @Nullable
or @NonNullable
arguments.
public static class Builder {
public Builder(Builder original);
public Builder setObjectValue(@Nullable Object value);
}
Builder setters may take @Nullable arguments for optional properties
It's often simpler to use a nullable value for second-degree input, especially in Kotlin, which utilizes default arguments instead of builders and overloads.
Additionally, @Nullable
setters will match them with their getters, which must be @Nullable
for optional properties.
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();
}
Common usage in Kotlin:
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.apply { optionalValue?.let { setOptionalValue(it) } }
.build()
fun createValue(optionalValue: OptionalValue? = null) =
Value.Builder()
.setOptionalValue(optionalValue)
.build()
The default value (if the setter isn't called), and the meaning of null
, must be properly documented in both the setter and the getter.
/**
* ...
*
* <p>Defaults to {@code null}, which means the optional value won't be used.
*/
تنظیمکنندههای سازنده را میتوان برای ویژگیهای قابل تغییر در جایی که تنظیمکنندهها در کلاس ساخته شده در دسترس هستند، ارائه کرد
If your class has mutable properties and needs a Builder
class, first ask yourself whether your class should actually have mutable properties.
Next, if you're certain that you need mutable properties, decide which of the following scenarios works better for your expected use case:
The built object should be immediately usable, thus setters should be provided for all relevant properties whether mutable or immutable.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Some additional calls may need to be made before the built object can be useful, thus setters shouldn't be provided for mutable properties.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
Don't mix the two scenarios.
Value v = new Value.Builder(requiredValue)
.setImmutableProperty(immutableValue)
.setUsefulMutableProperty(usefulValue)
.build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);
Builders shouldn't have getters
Getter should be on the built object, not the builder.
Builder setters must have corresponding getters on the built class
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();
}
Builder method naming
Builder methods names should use setFoo()
, addFoo()
or clearFoo()
style.
از کلاس های سازنده انتظار می رود که متد build() را اعلام کنند
Builder classes should declare a build()
method that returns an instance of the constructed object.
Builder build() methods must return @NonNull objects
A builder's build()
method is expected to return a nonnull instance of the constructed object. In the event that the object can't be created due to invalid parameters, validation can be deferred to the build method and an IllegalStateException
should be thrown.
Don't expose internal locks
Methods in the public API shouldn't use the synchronized
keyword. This keyword causes your object or class to be used as the lock, and because it's exposed to others, you may encounter unexpected side effects if other code outside your class starts using it for locking purposes.
Instead, perform any required locking against an internal, private object.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object();
public void doThing() {
synchronized (mThingLock) {
...
}
}
Accessor-styled methods should follow Kotlin property guidelines
When viewed from Kotlin sources, accessor-styled methods -- those using the get
, set
, or is
prefixes -- will also be available as Kotlin properties. For example, int getField()
defined in Java is available in Kotlin as the property val field: Int
.
For this reason, and to generally meet developer expectations around accessor method behavior, methods using accessor method prefixes should behave similarly to Java fields. Avoid using accessor-style prefixes when:
- The method has side effects -- prefer a more descriptive method name
- The method involves computationally expensive work -- prefer
compute
- The method involves blocking or otherwise long-running work to return a value, such as IPC or other I/O -- prefer
fetch
- The method blocks the thread until it can return a value -- prefer
await
- The method returns a new object instance on every call -- prefer
create
- The method may not successfully return a value -- prefer
request
Note that performing computationally expensive work once and caching the value for subsequent calls still counts as performing computationally expensive work. Jank isn't amortized across frames.
استفاده پیشوند برای متدهای دسترسی بولی است
This is the standard naming convention for boolean methods and fields in Java. Generally, boolean method and variable names should be written as questions that are answered by the return value.
Java boolean accessor methods should follow a set
/ is
naming scheme and fields should prefer is
, as in:
// 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;
Using set
/ is
for Java accessor methods or is
for Java fields will allow them to be used as properties from Kotlin:
obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return
Properties and accessor methods should generally use positive naming, for example Enabled
rather than Disabled
. Using negative terminology inverts the meaning of true
and false
and makes it more difficult to reason about behavior.
// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);
In cases where the boolean describes inclusion or ownership of a property, you may use has rather than is ; however, this will not work with Kotlin property syntax:
// 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();
Some alternative prefixes that may be more suitable include can and should :
// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();
// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();
Methods that toggle behaviors or features may use the is prefix and Enabled suffix:
// "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()
Similarly, methods that indicate the dependency on other behaviors or features may use is prefix and Supported or Required suffix:
// "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()
Generally, method names should be written as questions that are answered by the return value.
روش های ویژگی کاتلین
For a class property var foo: Foo
Kotlin will generate get
/ set
methods using a consistent rule: prepend get
and uppercase the first character for the getter, and prepend set
and uppercase the first character for the setter. The property declaration will produce methods named public Foo getFoo()
and public void setFoo(Foo foo)
, respectively.
If the property is of type Boolean
an additional rule applies in name generation: if the property name begins with is
, then get
isn't prepended for the getter method name, the property name itself is used as the getter. Therefore, prefer naming Boolean
properties with an is
prefix in order to follow the naming guideline:
var isVisible: Boolean
If your property is one of the aforementioned exceptions and begins with an appropriate prefix, use the @get:JvmName
annotation on the property to manually specify the appropriate name:
@get:JvmName("hasTransientState")
var hasTransientState: Boolean
@get:JvmName("canRecord")
var canRecord: Boolean
@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean
لوازم جانبی بیت ماسک
See Use @IntDef
for bitmask flags for API guidelines regarding defining bitmask flags.
Setters
Two setter methods should be provided: one that takes a full bitstring and overwrites all existing flags and another that takes a custom bitmask to allow more flexibility.
/**
* 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);
گیرندگان
One getter should be provided to obtain the full bitmask.
/**
* 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();
Use public instead of protected
Always prefer public
to protected
in public API. Protected access ends up being painful in the long run, because implementers have to override to provide public accessors in cases where external access by default would have been just as good.
Remember that protected
visibility doesn't prevent developers from calling an API -- it only makes it slightly more obnoxious.
Implement neither or both of equals() and hashCode()
If you override one, you must override the other.
پیاده سازی toString() برای کلاس های داده
Data classes are encouraged to override toString()
, to help developers debug their code.
مستندسازی کنید که آیا خروجی مربوط به رفتار برنامه است یا اشکال زدایی
Decide whether you want program behavior to rely on your implementation or not. For example, UUID.toString() and File.toString() document their specific format for programs to use. If you are exposing information for debugging only, like Intent , then imply inherit docs from the superclass.
Don't include extra information
All the information available from toString()
should also be available through the public API of the object. Otherwise, you are encouraging developers to parse and rely on your toString()
output, which will prevent future changes. A good practice is to implement toString()
using only the object's public API.
Discourage reliance on debug output
While it's impossible to prevent developers from depending on debug output, including the System.identityHashCode
of your object in its toString()
output will make it very unlikely that two different objects will have equal toString()
output.
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}
This can effectively discourage developers from writing test assertions like assertThat(a.toString()).isEqualTo(b.toString())
on your objects.
Use createFoo when returning newly created objects
Use the prefix create
, not get
or new
, for methods that will create return values, for example by constructing new objects.
When the method will create an object to return, make that clear in the method name.
public FooThing getFooThing() {
return new FooThing();
}
public FooThing createFooThing() {
return new FooThing();
}
Methods accepting File objects should also accept streams
Data storage locations on Android aren't always files on disk. For example, content passed across user boundaries is represented as content://
Uri
s. To enable processing of various data sources, APIs which accept File
objects should also accept InputStream
, OutputStream
, or both.
public void setDataSource(File file)
public void setDataSource(InputStream stream)
Take and return raw primitives instead of boxed versions
If you need to communicate missing or null values, consider using -1
, Integer.MAX_VALUE
, or Integer.MIN_VALUE
.
public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)
Avoiding class equivalents of primitive types avoids the memory overhead of these classes, method access to values, and, more importantly, autoboxing that comes from casting between primitive and object types. Avoiding these behaviors saves on memory and on temporary allocations that can lead to expensive and more frequent garbage collections.
از حاشیه نویسی برای روشن کردن پارامترهای معتبر و مقادیر برگشتی استفاده کنید
Developer annotations were added to help clarify allowable values in various situations. This makes it easier for tools to help developers when they supply incorrect values (for example, passing an arbitrary int
when the framework requires one of a specific set of constant values). Use any and all of the following annotations when appropriate:
پوچ پذیری
Explicit nullability annotations are required for Java APIs, but the concept of nullability is part of the Kotlin language and nullability annotations should never be used in Kotlin APIs.
@Nullable
: Indicates that a given return value, parameter, or field can be null:
@Nullable
public String getName()
public void setName(@Nullable String name)
@NonNull
: Indicates that a given return value, parameter, or field can't be null. Marking things as @Nullable
is relatively new to Android, so most of Android's API methods aren't consistently documented. Therefore we have a tri-state of "unknown, @Nullable
, @NonNull
" which is why @NonNull
is part of the API guidelines:
@NonNull
public String getName()
public void setName(@NonNull String name)
For Android platform docs, annotating your method parameters will automatically generate documentation in the form "This value may be null." unless "null" is explicitly used elsewhere in the parameter doc.
Existing "not really nullable" methods: Existing methods in the API without a declared @Nullable
annotation may be annotated @Nullable
if the method can return null
under specific, obvious circumstances (such as findViewById()
). Companion @NotNull requireFoo()
methods that throw IllegalArgumentException
should be added for developers who don't want to null check.
Interface methods: new APIs should add the proper annotation when implementing interface methods, like Parcelable.writeToParcel()
(ie, that method in the implementing class should be writeToParcel(@NonNull Parcel, int)
, not writeToParcel(Parcel, int)
); existing APIs that are lacking the annotations don't need to be "fixed", though.
Nullability enforcement
In Java, methods are recommended to perform input validation for @NonNull
parameters using Objects.requireNonNull()
and throw a NullPointerException
when the parameters are null. This is automatically performed in Kotlin.
منابع
Resource identifiers: Integer parameters that denote ids for specific resources should be annotated with the appropriate resource-type definition. There is an annotation for every type of resource, such as @StringRes
, @ColorRes
, and @AnimRes
, in addition to the catch-all @AnyRes
. به عنوان مثال:
public void setTitle(@StringRes int resId)
@IntDef برای مجموعه های ثابت
Magic constants: String
and int
parameters that are meant to receive one of a finite set of possible values denoted by public constants should be annotated appropriately with @StringDef
or @IntDef
. These annotations allow you to create a new annotation that you can use that works like a typedef for allowable parameters. به عنوان مثال:
/** @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);
Methods are recommended to check the validity of the annotated parameters and throw an IllegalArgumentException
if the parameter isn't part of the @IntDef
@IntDef for bitmask flags
The annotation can also specify that the constants are flags, and can be combined with & and 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 for string constant sets
There is also the @StringDef
annotation, which is exactly like @IntDef
in the previous section, but for String
constants. You can include multiple "prefix" values which are used to automatically emit documentation for all values.
@SdkConstant for SDK constants
@SdkConstant Annotate public fields when they are one of these SdkConstant
values: 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";
قابلیت پوچ پذیری سازگار را برای بازنویسی ها فراهم کنید
For API compatibility, the nullability of overrides should be compatible with the current nullability of the parent. The following table represents the compatibility expectations. Plainly, overrides should only be as restrictive or more restrictive than the element they override.
تایپ کنید | پدر و مادر | کودک |
---|---|---|
نوع برگشت | Unannotated | Unannotated or nonnull |
نوع برگشت | باطل شدنی | Nullable or nonnull |
نوع برگشت | Nonnull | Nonnull |
Fun argument | Unannotated | Unannotated or nullable |
Fun argument | باطل شدنی | باطل شدنی |
Fun argument | Nonnull | Nullable or nonnull |
Prefer non-nullable (such as @NonNull) arguments where possible
When methods are overloaded, prefer that all arguments are nonnull.
public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
This rule applies to overloaded property setters as well. The primary argument should be nonnull and clearing the property should be implemented as a separate method. This prevents "nonsense" calls where the developer must set trailing parameters even though they aren't required.
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()
Prefer non-nullable (such as @NonNull) return types for containers
For container types such as Bundle
or Collection
, return an empty -- and immutable, where applicable -- container. In cases where null
would be used to distinguish availability of a container, consider providing a separate boolean method.
@NonNull
public Bundle getExtras() { ... }
Nullability annotations for get and set pairs must agree
Get and set method pairs for a single logical property should always agree in their nullability annotations. Failing to follow this guideline will defeat Kotlin's property syntax, and adding disagreeing nullability annotations to existing property methods is therefore a source-breaking change for Kotlin users.
@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }
Return value in failure or error conditions
All APIs should permit apps to react to errors. Returning false
, -1
, null
, or other catch-all values of "something went wrong" don't tell a developer enough about the failure to set user expectations or accurately track reliability of their app in the field. When designing an API, imagine that you are building an app. If you encounter an error, does the API give you enough information to present it to the user or react appropriately?
- It's fine (and encouraged) to include detailed information in an exception message, but developers shouldn't have to parse it to handle the error appropriately. Verbose error codes or other information should be exposed as methods.
- Make sure your chosen error handling option gives you the flexibility to introduce new error types in the future. For
@IntDef
, that means including anOTHER
orUNKNOWN
value - when returning a new code, you can check the caller'stargetSdkVersion
to avoid returning an error code the app doesn't know about. For exceptions, have a common superclass that your exceptions implement, so that any code that handles that type will also catch and handle subtypes. - It should be difficult or impossible for a developer to accidentally ignore an error -- if your error is communicated by returning a value, annotate your method with
@CheckResult
.
Prefer throwing a ? extends RuntimeException
when a failure or error condition is reached due to something that the developer did wrong, for example ignoring constraints on input parameters or failing to check observable state.
Setter or action (for example, perform
) methods may return an integer status code if the action may fail as a result of asynchronously-updated state or conditions outside the developer's control.
Status codes should be defined on the containing class as public static final
fields, prefixed with ERROR_
, and enumerated in an @hide
@IntDef
annotation.
Method names should always begin with the verb, not the subject
The name of the method should always begin with the verb (such as get
, create
, reload
, etc.), not the object you're acting on.
public void tableReload() {
mTable.reload();
}
public void reloadTable() {
mTable.reload();
}
Prefer Collection types over arrays as return or parameter type
Generically typed collection interfaces provide several advantages over arrays, including stronger API contracts around uniqueness and ordering, support for generics, and a number of developer-friendly convenience methods.
استثنا برای اولیه ها
If the elements are primitives, do prefer arrays instead, in order to avoid the cost of auto-boxing. See Take and return raw primitives instead of boxed versions
استثنا برای کدهای حساس به عملکرد
In certain scenarios, where the API is used in performance-sensitive code (like graphics or other measure/layout/draw APIs), it is acceptable to use arrays instead of collections in order to reduce allocations and memory churn.
Exception for Kotlin
Kotlin arrays are invariant and the Kotlin language provides ample utility APIs around arrays, so arrays are on-par with List
and Collection
for Kotlin APIs intended to be accessed from Kotlin.
Prefer @NonNull collections
Always prefer @NonNull
for collection objects. When returning an empty collection, use the appropriate Collections.empty
method to return a low cost, correctly typed, and immutable collection object.
Where type annotations are supported, always prefer @NonNull
for collection elements.
You should also prefer @NonNull
when using arrays instead of collections (see previous item ). If object allocation is a concern, create a constant and pass it along - after all, an empty array is immutable. مثال:
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 APIs should prefer read-only (not Mutable
) return types for collections by default unless the API contract specifically requires a mutable return type.
Java APIs, however, should prefer mutable return types by default because the Android platform implementation of Java APIs doesn't yet provide a convenient implementation of immutable collections. The exception to this rule is Collections.empty
return types, which are immutable. In cases where mutability could be exploited by clients -- on purpose or by mistake -- to break the API's intended usage pattern, Java APIs should strongly consider returning a shallow copy of the collection.
@Nullable
public PermissionInfo[] getGrantedPermissions() {
return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
if (mPermissions == null) {
return Collections.emptySet();
}
return new ArraySet<>(mPermissions);
}
انواع بازگشتی که به وضوح قابل تغییر هستند
APIs that return collections should ideally not modify the returned collection object after returning. If the returned collection must change or be reused in some way -- for example, an adapted view of a mutable dataset -- the precise behavior of when the contents can change must be explicitly documented or follow established API naming conventions.
/**
* Returns a view of this object as a list of [Item]s.
*/
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
The Kotlin .asFoo()
convention is described below and permits the collection returned by .asList()
to change if the original collection changes.
Mutability of returned data-type objects
Similar to APIs that return collections, APIs that return data-type objects should ideally not modify the properties of the returned object after returning.
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)
}
In extremely limited cases, some performance-sensitive code may benefit from object pooling or reuse. Don't write your own object pool data structure and don't expose reused objects in public APIs. In either case, be extremely careful about managing concurrent access.
Use of vararg parameter type
Both Kotlin and Java APIs are encouraged to use vararg
in cases where the developer would be likely to create an array at the call site for the sole purpose of passing multiple, related parameters of the same type.
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);
Defensive copies
Both Java and Kotlin implementations of vararg
parameters compile to the same array-backed bytecode and as a result may be called from Java code with a mutable array. API designers are strongly encouraged to create a defensive shallow copy of the array parameter in cases where it will be persisted to a field or anonymous inner class.
public void setValues(SomeObject... values) {
this.values = Arrays.copyOf(values, values.length);
}
Note that creating a defensive copy doesn't provide any protection against concurrent modification between the initial method call and the creation of the copy, nor does it protect against mutation of the objects contained in the array.
Provide correct semantics with collection type parameters or returned types
List<Foo>
is default option, but consider other types to provide additional meaning:
Use
Set<Foo>
, if your API is indifferent to the order of elements and it doesn't allow duplicates or duplicates are meaningless.Collection<Foo>,
if your API is indifferent to the order and allows duplicates.
Kotlin conversion functions
Kotlin frequently uses .toFoo()
and .asFoo()
to obtain an object of a different type from an existing object where Foo
is the name of the conversion's return type. This is consistent with the familiar JDK Object.toString()
. Kotlin takes this further by using it for primitive conversions such as 25.toFloat()
.
The distinction between conversions named .toFoo()
and .asFoo()
is significant:
Use .toFoo() when creating a new, independent object
Like .toString()
, a "to" conversion returns a new, independent object. If the original object is modified later, the new object won't reflect those changes. Similarly, if the new object is modified later, the old object won't reflect those changes.
fun Foo.toBundle(): Bundle = Bundle().apply {
putInt(FOO_VALUE_KEY, value)
}
هنگام ایجاد یک بسته بندی وابسته، شی تزئین شده یا ریخته گری از .asFoo() استفاده کنید
Casting in Kotlin is performed using the as
keyword. It reflects a change in interface but not a change in identity. When used as a prefix in an extension function, .asFoo()
decorates the receiver. A mutation in the original receiver object will be reflected in the object returned by asFoo()
. A mutation in the new Foo
object may be reflected in the original object.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
collect {
emit(it)
}
}
Conversion functions should be written as extension functions
Writing conversion functions outside of both the receiver and the result class definitions reduces coupling between types. An ideal conversion needs only public API access to the original object. This proves by example that a developer can write analogous conversions to their own preferred types as well.
Throw appropriate specific exceptions
Methods must not throw generic exceptions such as java.lang.Exception
or java.lang.Throwable
, instead an appropriate specific exception has to be used like java.lang.NullPointerException
to allow developers to handle exceptions without being overly broad.
Errors that are unrelated to the arguments provided directly to the publicly invoked method should throw java.lang.IllegalStateException
instead of java.lang.IllegalArgumentException
or java.lang.NullPointerException
.
Listeners and callbacks
These are the rules around the classes and methods used for listener and callback mechanisms.
نام کلاس های برگشت به تماس باید مفرد باشد
Use MyObjectCallback
instead of MyObjectCallbacks
.
نام روش های برگشت به تماس باید با فرمت موجود باشد
onFooEvent
signifies that FooEvent
is happening and that the callback should act in response.
Past versus present tense should describe timing behavior
Callback methods regarding events should be named to indicate whether the event has already happened or is in the process of happening.
For example, if the method is called after a click action has been performed:
public void onClicked()
However, if the method is responsible for performing the click action:
public boolean onClick()
Callback registration
When a listener or callback can be added or removed from an object, the associated methods should be named add and remove or register and unregister. Be consistent with the existing convention used by the class or by other classes in the same package. When no such precedent exists, prefer add and remove.
Methods involving registering or unregistering callbacks should specify the whole name of the callback type.
public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);
Avoid getters for callbacks
Don't add getFooCallback()
methods. This is a tempting escape hatch for cases where developers may want to chain an existing callback together with their own replacement, but it is brittle and makes the current state difficult to reason about for component developers. به عنوان مثال،
- Developer A calls
setFooCallback(a)
- Developer B calls
setFooCallback(new B(getFooCallback()))
- Developer A wishes to remove its callback
a
and has no way to do so without knowledge ofB
's type, andB
having been built to allow such modifications of its wrapped callback.
برای کنترل ارسال پاسخ به تماس، Executor را بپذیرید
When registering callbacks that have no explicit threading expectations (pretty much anywhere outside the UI toolkit), it is strongly encouraged to include an Executor
parameter as part of registration to allow the developer to specify the thread upon which the callbacks will be invoked.
public void registerFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
As an exception to our usual guidelines about optional parameters , it is acceptable to provide an overload omitting the Executor
even though it isn't the final argument in the parameter list. If the Executor
isn't provided, the callback should be invoked on the main thread using Looper.getMainLooper()
and this should be documented on the associated overloaded method.
/**
* ...
* 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
implementation gotchas: Note that the following is a valid executor!
public class SynchronousExecutor implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
This means that when implementing APIs that take this form, your incoming binder object implementation on the app process side must call Binder.clearCallingIdentity()
before invoking the app's callback on the app-supplied Executor
. This way any app code that uses binder identity (such as Binder.getCallingUid()
) for permission checks correctly attributes the code running to the app and not to the system process calling into the app. If users of your API want the UID or PID information of the caller then this should be an explicit part of your API surface, rather than implicit based on where the Executor
they supplied ran.
Specifying an Executor
should be supported by your API. In performance-critical cases apps may need to run code either immediately or synchronously with feedback from your API. Accepting an Executor
permits this. Defensively creating an additional HandlerThread
or similar to trampoline from defeats this desirable use case.
If an app is going to run expensive code somewhere in their own process, let them . The workarounds that app developers will find to overcome your restrictions will be much harder to support in the long term.
Exception for single callback: when the nature of the events being reported calls for only supporting a single callback instance, use the following style:
public void setFooCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull FooCallback callback)
public void clearFooCallback()
به جای Handler از Executor استفاده کنید
Android's Handler
was used as a standard for redirecting callback execution to a specific Looper
thread in the past. This standard was changed to prefer Executor
as most app developers manage their own thread pools, making the main or UI thread the only Looper
thread available to the app. Use Executor
to give developers the control they need to reuse their existing/preferred execution contexts.
Modern concurrency libraries like kotlinx.coroutines or RxJava provide their own scheduling mechanisms that perform their own dispatch when needed, which makes it important to provide the ability to use a direct executor (such as Runnable::run
) to avoid latency from double thread hops. For example, one hop to post to a Looper
thread using a Handler
followed by another hop from the app's concurrency framework.
Exceptions to this guideline are rare. Common appeals for an exception include:
I have to use a Looper
because I need a Looper
to epoll
for the event. This exception request is granted as the benefits of Executor
can't be realized in this situation.
I don't want app code to block my thread publishing the event. This exception request is typically not granted for code that runs in an app process. Apps that get this wrong are only hurting themselves, not impacting overall system health. Apps that get it right or use a common concurrency framework shouldn't pay additional latency penalties.
Handler
is locally consistent with other similar APIs in the same class. This exception request is granted situationally. Preference is for Executor
-based overloads to be added, migrating Handler
implementations to use the new Executor
implementation. ( myHandler::post
is a valid Executor
!) Depending on the size of the class, number of existing Handler
methods, and likelihood that developers would need to use existing Handler
based methods alongside the new method, an exception may be granted to add a new Handler
-based method.
Symmetry in registration
If there is a way to add or register something, there should also be a way to remove/unregister it. روش
registerThing(Thing)
should have a matching
unregisterThing(Thing)
یک شناسه درخواست ارائه دهید
If it is reasonable for a developer to reuse a callback, provide an identifier object to tie the callback to the request.
class RequestParameters {
public int getId() { ... }
}
class RequestExecutor {
public void executeRequest(
RequestParameters parameters,
Consumer<RequestParameters> onRequestCompletedListener) { ... }
}
Multiple-method callback objects
Multiple-method callbacks should prefer interface
and use default
methods when adding to previously-released interfaces. Previously, this guideline recommended abstract class
due to the lack of default
methods in Java 7.
public interface MostlyOptionalCallback {
void onImportantAction();
default void onOptionalInformation() {
// Empty stub, this method is optional.
}
}
از android.os.OutcomeReceiver هنگام مدلسازی فراخوانی تابع غیرمسدود کننده استفاده کنید
OutcomeReceiver<R,E>
reports a result value R
when successful or E : Throwable
otherwise - the same things a plain method call can do. Use OutcomeReceiver
as the callback type when converting a blocking method that returns a result or throws an exception to a nonblocking async method:
interface FooType {
// Before:
public FooResult requestFoo(FooRequest request);
// After:
public void requestFooAsync(FooRequest request, Executor executor,
OutcomeReceiver<FooResult, Throwable> callback);
}
Async methods converted in this way always return void
. Any result that requestFoo
would return is instead reported to requestFooAsync
's callback
parameter's OutcomeReceiver.onResult
by calling it on the provided executor
. Any exception that requestFoo
would throw is instead reported to the OutcomeReceiver.onError
method in the same way.
Using OutcomeReceiver
for reporting async method results also affords a Kotlin suspend fun
wrapper for async methods using the Continuation.asOutcomeReceiver
extension from androidx.core:core-ktx
:
suspend fun FooType.requestFoo(request: FooRequest): FooResult =
suspendCancellableCoroutine { continuation ->
requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver())
}
Extensions like this enable Kotlin clients to call nonblocking async methods with the convenience of a plain function call without blocking the calling thread. These 1-1 extensions for platform APIs may be offered as part of the androidx.core:core-ktx
artifact in Jetpack when combined with standard version compatibility checks and considerations. See the documentation for asOutcomeReceiver for more information, cancellation considerations and samples.
Async methods that don't match the semantics of a method returning a result or throwing an exception when its work is complete shouldn't use OutcomeReceiver
as a callback type. Instead consider one of the other options listed in the following section.
Prefer functional interfaces over creating new single abstract method (SAM) types
API level 24 added the java.util.function.*
( reference docs ) types, which offer generic SAM interfaces such as Consumer<T>
that are suitable for use as callback lambdas. In many cases, creating new SAM interfaces provides little value in terms of type safety or communicating intent while unnecessarily expanding the Android API surface area.
Consider using these generic interfaces, rather than creating new ones:
-
Runnable
:() -> Unit
-
Supplier<R>
:() -> R
-
Consumer<T>
:(T) -> Unit
-
Function<T,R>
:(T) -> R
-
Predicate<T>
:(T) -> Boolean
- many more available in reference docs
Placement of SAM parameters
SAM parameters should be placed last to enable idiomatic usage from Kotlin, even if the method is being overloaded with additional parameters.
public void schedule(Runnable runnable)
public void schedule(int delay, Runnable runnable)
اسناد
These are rules about the public docs (Javadoc) for APIs.
All public APIs must be documented
All public APIs must have sufficient documentation to explain how a developer would use the API. Assume the developer found the method using autocomplete or while browsing through API reference docs and has a minimal amount of context from the adjacent API surface (for example, the same class).
روش ها
Method parameters and return values must be documented using @param
and @return
docs annotations, respectively. Format the Javadoc body as though it's preceded by "This method...".
In cases where a method takes no parameters, has no special considerations, and returns what the method name says it does, you can omit the @return
and write docs similar to:
/**
* Returns the priority of the thread.
*/
@IntRange(from = 1, to = 10)
public int getPriority() { ... }
Always use links in Javadoc
Docs should link to other docs for related constants, methods, and other elements. Use Javadoc tags (for example, @see
and {@link foo}
), not just plain-text words.
For the following source example:
public static final int FOO = 0;
public static final int BAR = 1;
Don't use raw text or code font:
/**
* 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) { ... }
Instead, use links:
/**
* Sets value to one of {@link #FOO} or {@link #BAR}.
*
* @param value the value being set
*/
public void setValue(@ValueType int value) { ... }
Note that using an IntDef
annotation such as @ValueType
on a parameter automatically generates documentation specifying the allowed types. See the guidance on annotations for more information on IntDef
.
Run update-api or docs target when adding Javadoc
This rule is particularly important when adding @link
or @see
tags, and make sure the output looks as expected. ERROR output in Javadoc is often due to bad links. Either the update-api
or docs
Make target performs this check, but the docs
target might be quicker if you're only changing Javadoc and don't otherwise need to run the update-api
target.
Use {@code foo} to distinguish Java values
Wrap Java values like true
, false
, and null
with {@code...}
to distinguish them from documentation text.
When writing documentation in Kotlin sources, you can wrap code with backticks like you would for Markdown.
@param and @return summaries should be a single sentence fragment
Parameter and return value summaries should start with a lowercase character and contain only a single sentence fragment. If you have additional information that extends beyond a single sentence, move it to the method Javadoc body:
/**
* @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.
*/
Should be changed to:
/**
* @param e element to be appended to this list, must be non-{@code null}
* @return {@code true} on success, {@code false} otherwise
*/
Docs annotations need explanations
Document why annotations @hide
and @removed
are hidden from the public API. Include instructions for how to replace API elements marked with the @deprecated
annotation.
Use @throws to document exceptions
If a method throws a checked exception, for example IOException
, document the exception with @throws
. For Kotlin-sourced APIs intended for use by Java clients, annotate functions with @Throws
.
If a method throws an unchecked exception indicating a preventable error, for example IllegalArgumentException
or IllegalStateException
, document the exception with an explanation of why the exception is thrown. The thrown exception should also indicate why it was thrown.
Certain cases of unchecked exception are considered implicit and don't need to be documented, such as NullPointerException
or IllegalArgumentException
where an argument doesn't match an @IntDef
or similar annotation that embeds the API contract into the method signature:
/**
* ...
* @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.");
}
// ...
Or, in 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")
}
}
// ...
If the method invokes asynchronous code that might throw exceptions, consider how the developer finds out about and responds to such exceptions. Typically this involves forwarding the exception to a callback and documenting the exceptions thrown on the method that receives them. Asynchronous exceptions shouldn't be documented with @throws
unless they're actually rethrown from the annotated method.
End the first sentence of docs with a period
The Doclava tool parses docs simplistically, ending the synopsis doc (the first sentence, used in the quick description at the top of the class docs) as soon as it sees a period (.) followed by a space. این باعث دو مشکل می شود:
- If a short doc doesn't end with a period, and if that member has inherited docs that are picked up by the tool, then the synopsis also picks up those inherited docs. For example, see
actionBarTabStyle
in theR.attr
docs , which has the description of the dimension added into the synopsis. - Avoid "eg" in the first sentence for the same reason, because Doclava ends the synopsis docs after "g.". For example, see
TEXT_ALIGNMENT_CENTER
inView.java
. Note that Metalava automatically corrects this error by inserting a nonbreaking space after the period; however, don't make this mistake in the first place.
Format docs to be rendered in HTML
Javadoc is rendered in HTML, so format these docs accordingly:
Line breaks should use an explicit
<p>
tag. Don't add a closing</p>
tag.Don't use ASCII to render lists or tables.
Lists should use
<ul>
or<ol>
for unordered and ordered, respectively. Each item should begin with an<li>
tag, but doesn't need a closing</li>
tag. A closing</ul>
or</ol>
tag is required after the last item.Tables should use
<table>
,<tr>
for rows,<th>
for headers, and<td>
for cells. All table tags require matching closing tags. You can useclass="deprecated"
on any tag to denote deprecation.To create inline code font, use
{@code foo}
.To create code blocks, use
<pre>
.All text inside a
<pre>
block is parsed by the browser, so be careful with brackets<>
. You can escape them with<
and>
HTML entities.Alternatively, you can leave raw brackets
<>
in your code snippet if you wrap the offending sections in{@code foo}
. به عنوان مثال:<pre>{@code <manifest>}</pre>
Follow the API reference style guide
To provide consistency in the style for class summaries, method descriptions, parameter descriptions, and other items, follow the recommendations in the official Java language guidelines at How to Write Doc Comments for the Javadoc Tool .
Android Framework-specific rules
These rules are about APIs, patterns, and data structures that are specific to APIs and behaviors built into the Android framework (for example, Bundle
or Parcelable
).
Intent builders should use the create*Intent() pattern
Creators for intents should use methods named createFooIntent()
.
Use Bundle instead of creating new general-purpose data structures
Avoid creating new general-purpose data structures to represent arbitrary key to typed value mappings. Instead, consider using Bundle
.
This typically comes up when writing platform APIs that serve as communication channels between nonplatform apps and services, where the platform doesn't read the data sent across the channel and the API contract may be partially defined outside of the platform (for example, in a Jetpack library).
In cases where the platform does read the data, avoid using Bundle
and prefer a strongly typed data class.
Parcelable implementations must have public CREATOR field
Parcelable inflation is exposed through CREATOR
, not raw constructors. If a class implements Parcelable
, then its CREATOR
field must also be a public API and the class constructor taking a Parcel
argument must be private.
Use CharSequence for UI strings
When a string is presented in a user interface, use CharSequence
to allow for Spannable
instances.
If it's just a key or some other label or value that isn't visible to users, String
is fine.
Avoid using Enums
IntDef
must be used over enums in all platform APIs, and should be strongly considered in unbundled, library APIs. Use enums only when you're certain that new values won't be added.
Benefits of IntDef
:
- Enables adding values over time
- Kotlin
when
statements can fail at runtime if they become no-longer-exhaustive due to an added enum value in platform.
- Kotlin
- No classes or objects used at runtime, only primitives
- While R8 or minfication can avoid this cost for unbundled library APIs, this optimization can't affect platform API classes.
Benefits of Enum
- Idiomatic language feature of Java, Kotlin
- Enables exhaustive switch,
when
statement usage- Note - values must not change over time, see previous list
- Clearly scoped, and discoverable naming
- Enables compile time verification
- For example, a
when
statement in Kotlin that returns a value
- For example, a
- Is a functioning class that can implement interfaces, have static helpers, expose member or extension methods, and expose fields.
Follow Android package layering hierarchy
The android.*
package hierarchy has an implicit ordering, where lower-level packages can't depend on higher-level packages.
Avoid referring to Google, other companies, and their products
The Android platform is an open-source project and aims to be vendor neutral. The API should be generic and equally usable by system integrators or apps with the requisite permissions.
Parcelable implementations should be final
Parcelable classes defined by the platform are always loaded from framework.jar
, so it is invalid for an app to try overriding a Parcelable
implementation.
If the sending app extends a Parcelable
, the receiving app won't have the sender's custom implementation to unpack with. Note about backward compatibility: if your class historically wasn't final, but didn't have a publicly available constructor, you still can mark it final
.
Methods calling into system process should rethrow RemoteException as RuntimeException
RemoteException
is typically thrown by internal AIDL, and indicates that the system process has died, or the app is trying to send too much data. In both cases, public API should rethrow as a RuntimeException
to prevent apps from persisting security or policy decisions.
If you know the other side of a Binder
call is the system process, this boilerplate code is the best-practice:
try {
...
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
Throw specific exceptions for API changes
Public API behaviors might change across API levels and cause app crashes (for instance to enforce new security policies).
When the API needs to throw for a request that was previously valid, throw a new specific exception instead of a generic one. For example, ExportedFlagRequired
instead of SecurityException
(and ExportedFlagRequired
can extend SecurityException
).
This will help app developers and tools detect API behavior changes.
Implement copy constructor instead of clone
Use of the Java clone()
method is strongly discouraged due to the lack of API contracts provided by the Object
class and difficulties inherent in extending classes that use clone()
. Instead, use a copy constructor that takes an object of the same type.
/**
* Constructs a shallow copy of {@code other}.
*/
public Foo(Foo other)
Classes that rely on a Builder for construction should consider adding a Builder copy constructor to allow modifications to the copy.
public class Foo {
public static final class Builder {
/**
* Constructs a Foo builder using data from {@code other}.
*/
public Builder(Foo other)
Use ParcelFileDescriptor over FileDescriptor
The java.io.FileDescriptor
object has a poor definition of ownership, which can result in obscure use-after-close bugs. Instead, APIs should return or accept ParcelFileDescriptor
instances. Legacy code can convert between PFD and FD if needed using dup() or getFileDescriptor() .
Avoid using odd-sized numerical values
Avoid using short
or byte
values directly, because they often limit how you might be able to evolve the API in the future.
Avoid using BitSet
java.util.BitSet
is great for implementation but not for public API. It's mutable, requires an allocation for high-frequency method calls, and doesn't provide semantic meaning for what each bit represents.
For high-performance scenarios, use an int
or long
with @IntDef
. For low-performance scenarios, consider a Set<EnumType>
. For raw binary data, use byte[]
.
Prefer android.net.Uri
android.net.Uri
is the preferred encapsulation for URIs in Android APIs.
Avoid java.net.URI
, because it is overly strict in parsing URIs, and never use java.net.URL
, because its definition of equality is severely broken.
Hide annotations marked as @IntDef, @LongDef, or @StringDef
Annotations marked as @IntDef
, @LongDef
, or @StringDef
denote a set of valid constants that can be passed to an API. However, when they are exported as APIs themselves, the compiler inlines the constants and only the (now useless) values remain in the annotation's API stub (for the platform) or JAR (for libraries).
As such, usages of these annotations must be marked with the @hide
docs annotation in the platform or @RestrictTo.Scope.LIBRARY)
code annotation in libraries. They must be marked @Retention(RetentionPolicy.SOURCE)
in both cases to prevent them from appearing in API stubs or JARs.
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
When building the platform SDK and library AARs, a tool extracts the annotations and bundles them separately from the compiled sources. Android Studio reads this bundled format and enforces the type definitions.
Don't add new setting provider keys
Don't expose new keys from Settings.Global
, Settings.System
, or Settings.Secure
.
Instead, add a proper getter and setter Java API in a relevant class, which is typically a "manager" class. Add a listener mechanism or a broadcast to notify clients of changes as needed.
SettingsProvider
settings have a number of problems compared to getters/setters:
- No type safety.
- No unified way to provide a default value.
- No proper way to customize permissions.
- For example, it's not possible to protect your setting with a custom permission.
- No proper way to add custom logic properly.
- For example, it's not possible to change setting A's value depending on setting B's value.
Example: Settings.Secure.LOCATION_MODE
has existed for a long time, but the location team has deprecated it for a proper Java API LocationManager.isLocationEnabled()
and the MODE_CHANGED_ACTION
broadcast, which gave the team a lot more flexibility, and the semantics of the APIs are a lot clearer now.
Don't extend Activity and AsyncTask
AsyncTask
is an implementation detail. Instead, expose a listener or, in androidx, a ListenableFuture
API instead.
Activity
subclasses are impossible to compose. Extending activity for your feature makes it incompatible with other features that require users to do the same. Instead, rely on composition by using tools such as LifecycleObserver .
Use the Context's getUser()
Classes bound to a Context
, such as anything returned from Context.getSystemService()
should use the user bound to the Context
instead of exposing members that target specific users.
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);
}
}
Exception: A method may accept a user argument if it accepts values that don't represent a single user, such as UserHandle.ALL
.
Use UserHandle instead of plain ints
UserHandle
is preferred to provide type safety and avoid conflating user IDs with uids.
Foobar getFoobarForUser(UserHandle user);
Foobar getFoobarForUser(int userId);
Where unavoidable, an int
representing a user ID must be annotated with @UserIdInt
.
Foobar getFoobarForUser(@UserIdInt int user);
Prefer listeners or callbacks to broadcast intents
Broadcast intents are very powerful, but they've resulted in emergent behaviors that can negatively impact system health, and so new broadcast intents should be added judiciously.
Here are some specific concerns which result in us discouraging the introduction of new broadcast intents:
When sending broadcasts without the
FLAG_RECEIVER_REGISTERED_ONLY
flag, they force-start any apps that aren't already running. While this can sometimes be an intended outcome, it can result in stampeding of dozens of apps, negatively impacting system health. We'd recommend using alternative strategies, such asJobScheduler
, to better coordinate when various preconditions are met.When sending broadcasts, there is little ability to filter or adjust the content delivered to apps. This makes it difficult or impossible to respond to future privacy concerns, or introduce behavior changes based on the target SDK of the receiving app.
Since broadcast queues are a shared resource, they can become overloaded and may not result in timely delivery of your event. We've observed several broadcast queues in the wild which have an end-to-end latency of 10 minutes or longer.
For these reasons, we encourage new features to consider using listeners or callbacks or other facilities such as JobScheduler
instead of broadcast intents.
In cases where broadcast intents still remain the ideal design, here are some best-practices that should be considered:
- If possible, use
Intent.FLAG_RECEIVER_REGISTERED_ONLY
to limit your broadcast to apps that are already running. For example,ACTION_SCREEN_ON
uses this design to avoid waking up apps. - If possible, use
Intent.setPackage()
orIntent.setComponent()
to target the broadcast at a specific app of interest. For example,ACTION_MEDIA_BUTTON
uses this design to focus on the current app handling playback controls. - If possible, define your broadcast as a
<protected-broadcast>
to prevent malicious apps from impersonating the OS.
Intents in system-bound developer services
Services that are intended to be extended by the developer and bound by the system, for example abstract services like NotificationListenerService
, may respond to an Intent
action from the system. Such services should meet the following criteria:
- Define a
SERVICE_INTERFACE
string constant on the class containing the fully-qualified class name of the service. This constant must be annotated with@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
. - Document on the class that a developer must add an
<intent-filter>
to theirAndroidManifest.xml
in order to receive Intents from the platform. - Strongly consider adding a system-level permission to prevent rogue apps from sending
Intent
s to developer services.
Kotlin-Java interop
See the official Android Kotlin-Java interop guide for a full list of guidelines. Select guidelines have been copied to this guide to improve discoverability.
API visibility
Some Kotlin APIs, like suspend fun
s, aren't intended to be used by Java developers; however, don't attempt to control language-specific visibility using @JvmSynthetic
as it has side-effects on how the API is presented in debuggers that make debugging more difficult.
See the Kotlin-Java interop guide or Async guide for specific guidance.
اشیاء همراه
Kotlin uses companion object
to expose static members. In some cases, these will show up from Java on an inner class named Companion
rather than on the containing class. Companion
classes may show as empty classes in API text files -- that is working as intended.
To maximize compatibility with Java, annotate companion objects' non-constant fields with @JvmField
and public functions with @JvmStatic
to expose them directly on the containing class.
companion object {
@JvmField val BIG_INTEGER_ONE = BigInteger.ONE
@JvmStatic fun fromPointF(pointf: PointF) {
/* ... */
}
}
Evolution of Android platform APIs
This section describes policies regarding what types of changes you can make to existing Android APIs and how you should implement those changes to maximize compatibility with existing apps and codebases.
Binary-breaking changes
Avoid binary-breaking changes in finalized public API surfaces. These types of changes generally raise errors when running make update-api
, but there might be edge cases that Metalava's API check doesn't catch. When in doubt, refer to the Eclipse Foundation's Evolving Java-based APIs guide for a detailed explanation of what types of API changes are compatible in Java. Binary-breaking changes in hidden (for example, system) APIs should follow the deprecate/replace cycle.
Source-breaking changes
We discourage source-breaking changes even if they aren't binary breaking. One example of a binary compatible but source-breaking change is adding a generic to an existing class, which is binary compatible but can introduce compilation errors due to inheritance or ambiguous references. Source-breaking changes won't raise errors when running make update-api
, so you must take care to understand the impact of changes to existing API signatures.
In some cases, source-breaking changes become necessary to improve the developer experience or code correctness. For example, adding nullability annotations to Java sources improves interoperability with Kotlin code and reduces the likelihood of errors, but often requires changes -- sometimes significant changes -- to source code.
Changes to private APIs
You can change APIs annotated with @TestApi
at any time.
You must preserve APIs annotated with @SystemApi
for three years. You must remove or refactor a system API on the following schedule:
- API y - Added
- API y+1 - Deprecation
- Mark the code with
@Deprecated
. - Add replacements, and link to the replacement in the Javadoc for the deprecated code using the
@deprecated
docs annotation. - During the development cycle, file bugs against internal users telling them the API is being deprecated. This helps validate that the replacement APIs are adequate.
- Mark the code with
- API y+2 - Soft removal
- Mark the code with
@removed
. - Optionally, throw or no-op for apps that target the current SDK level for the release.
- Mark the code with
- API y+3 - Hard removal
- Completely remove the code from the source tree.
منسوخ شدن
We consider deprecation an API change, and it can occur in a major (such as letter) release. Use the @Deprecated
source annotation and @deprecated <summary>
docs annotation together when deprecating APIs. Your summary must include a migration strategy. This strategy might link to a replacement API or explain why you shouldn't use the API:
/**
* Simple version of ...
*
* @deprecated Use the {@link androidx.fragment.app.DialogFragment}
* class with {@link androidx.fragment.app.FragmentManager}
* instead.
*/
@Deprecated
public final void showDialog(int id)
You must also deprecate APIs defined in XML and exposed in Java, including attributes and styleable properties exposed in the android.R
class, with a summary:
<!-- Attribute whether the accessibility service ...
{@deprecated Not used by the framework}
-->
<attr name="canRequestEnhancedWebAccessibility" format="boolean" />
When to deprecate an API
Deprecations are most useful for discouraging adoption of an API in new code .
We also require that you mark APIs as @deprecated
before they're @removed
, but this doesn't provide strong motivation for developers to migrate away from an API they're already using.
Before deprecating an API, consider the impact on developers. The effects of deprecating an API include:
-
javac
emits a warning during compilation.- Deprecation warnings can't be suppressed globally or baselined, so developers using
-Werror
need to individually fix or suppress every usage of a deprecated API before they can update their compile SDK version. - Deprecation warnings on imports of deprecated classes can't be suppressed. As a result, developers need to inline the fully qualified class name for every usage of a deprecated class before they can update their compile SDK version.
- Deprecation warnings can't be suppressed globally or baselined, so developers using
- Documentation on
d.android.com
shows a deprecation notice. - IDEs like Android Studio show a warning at the API usage site.
- IDEs might down-rank or hide the API from auto-complete.
As a result, deprecating an API can discourage the developers who are the most concerned about code health (those using -Werror
) from adopting new SDKs. Developers who aren't concerned about warnings in their existing code are likely to ignore deprecations altogether.
An SDK that introduces a large number of deprecations makes both of these cases worse.
For this reason, we recommend deprecating APIs only in cases where:
- We plan to
@remove
the API in a future release. - API use leads to incorrect or undefined behavior that we can't fix without breaking compatibility.
When you deprecate an API and replace it with a new API, we strongly recommend adding a corresponding compatibility API to a Jetpack library like androidx.core
to simplify supporting both old and new devices.
We don't recommend deprecating APIs that work as intended in current and future releases:
/**
* ...
* @deprecated Use {@link #doThing(int, Bundle)} instead.
*/
@Deprecated
public void doThing(int action) {
...
}
public void doThing(int action, @Nullable Bundle extras) {
...
}
Deprecation is appropriate in cases where APIs can no longer maintain their documented behaviors:
/**
* ...
* @deprecated No longer displayed in the status bar as of API 21.
*/
@Deprecated
public RemoteViews tickerView;
Changes to deprecated APIs
You must maintain the behavior of deprecated APIs. This means test implementations must remain the same, and tests must continue to pass after you have deprecated the API. If the API doesn't have tests, you should add tests.
Don't expand deprecated API surfaces in future releases. You can add lint correctness annotations (for example, @Nullable
) to an existing deprecated API, but shouldn't add new APIs.
Don't add new APIs as deprecated. If any APIs were added and subsequently deprecated within a prerelease cycle (thus would initially enter the public API surface as deprecated), you must remove them before finalizing the API.
Soft removal
Soft removal is a source-breaking change, and you should avoid it in public APIs unless the API Council explicitly approves it. For system APIs, you must deprecate the API for the duration of a major release before a soft removal. Remove all docs references to the APIs and use the @removed <summary>
docs annotation when soft-removing APIs. Your summary must include the reason for removal and can include a migration strategy, as we explained in Deprecation .
The behavior of soft-removed APIs can be maintained as is, but more importantly must be preserved such that existing callers won't crash when calling the API. In some cases, that might mean preserving behavior.
Test coverage must be maintained, but the content of the tests might need to change to accommodate for behavioral changes. Tests must still validate that existing callers don't crash at run time. You can maintain the behavior of soft-removed APIs as is, but more importantly, you must preserve it such that existing callers won't crash when calling the API. In some cases, that might mean preserving behavior.
You must maintain test coverage, but the content of the tests might need to change to accommodate behavioral changes. Tests must still validate that existing callers don't crash at run time.
At a technical level, we remove the API from the SDK stub JAR and compile-time classpath using the @remove
Javadoc annotation, but it still exists on the run-time classpath -- similar to @hide
APIs:
/**
* Ringer volume. This is ...
*
* @removed Not functional since API 2.
*/
public static final String VOLUME_RING = ...
From an app developer perspective, the API no longer appears in auto-complete and source code that references the API won't compile when the compileSdk
is equal to or later than the SDK at which the API was removed; however, source code continues to compile successfully against earlier SDKs and binaries that reference the API continue to work.
Certain categories of API must not be soft removed. You must not soft remove certain categories of API.
روش های انتزاعی
You must not soft remove abstract methods on classes that developers might extend. Doing so makes it impossible for developers to successfully extend the class across all SDK levels.
In rare cases where it was never and won't be possible for developers to extend a class, you can still soft remove abstract methods.
Hard removal
Hard removal is a binary-breaking change and should never occur in public APIs.
Discouraged annotation
We use the @Discouraged
annotation to indicate that we don't recommend an API in most (>95%) cases. Discouraged APIs differ from deprecated APIs in that there exists a narrow critical use case that prevents deprecation. When you mark an API as discouraged, you must provide an explanation and an alternative solution:
@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);
}
You shouldn't add new APIs as discouraged.
Changes to the behavior of existing APIs
In some cases, you might want to change the implementation behavior of an existing API. For example, in Android 7.0 we improved DropBoxManager
to clearly communicate when developers tried posting events that were too large to send across Binder
.
However, to avoid causing problems for existing apps, we strongly recommend preserving a safe behavior for older apps. We've historically guarded these behavior changes based on the ApplicationInfo.targetSdkVersion
of the app, but we've recently migrated to require using the App Compatibility Framework. Here's an example of how to implement a behavior change using this new framework:
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
public class MyClass {
@ChangeId
// This means the change will be enabled for target SDK R and higher.
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.R)
// Use a bug number as the value, provide extra detail in the bug.
// FOO_NOW_DOES_X will be the change name, and 123456789 the change ID.
static final long FOO_NOW_DOES_X = 123456789L;
public void doFoo() {
if (CompatChanges.isChangeEnabled(FOO_NOW_DOES_X)) {
// do the new thing
} else {
// do the old thing
}
}
}
Using this App Compatibility Framework design enables developers to temporarily disable specific behavior changes during preview and beta releases as part of debugging their apps, instead of forcing them to adjust to dozens of behavior changes simultaneously.
سازگاری رو به جلو
Forward compatibility is a design characteristic that allows a system to accept input intended for a later version of itself. In the case of API design, you must pay special attention to the initial design as well as future changes because developers expect to write code once, test it once, and have it run everywhere without issue.
The following cause the most common forward-compatibility issues in Android:
- Adding new constants to a set (such as
@IntDef
orenum
) previously assumed to be complete (for example, whereswitch
has adefault
that throws an exception). - Adding support for a feature that isn't captured directly in the API surface (for example, support for assigning
ColorStateList
-type resources in XML where previously only<color>
resources were supported). - Loosening restrictions on run-time checks, for example removing a
requireNotNull()
check that was present on lower versions.
In all of these cases, developers find out that something is wrong only at run time. Worse, they might find out as a result of crash reports from older devices in the field.
Additionally, these cases are all technically valid API changes. They don't break binary or source compatibility and API lint won't catch any of these issues.
As a result, API designers must pay careful attention when modifying existing classes. Ask the question, "Is this change going to cause code that's written and tested only against the latest version of the platform to fail on lower versions?"
XML schemas
If an XML schema serves as a stable interface between components, that schema must be explicitly specified and must evolve in a backward-compatible manner, similar to other Android APIs. For example, the structure of XML elements and attributes must be preserved similar to how methods and variables are maintained on other Android API surfaces.
XML deprecation
If you'd like to deprecate an XML element or attribute, you can add the xs:annotation
marker, but you must continue to support any existing XML files by following the typical @SystemApi
evolution lifecycle.
<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>
Element types must be preserved
Schemas support the sequence
element, choice
element and all
elements as child elements of complexType
element. However, these child elements differ in the number and order of their child elements, so modifying an existing type would be an incompatible change.
If you want to modify an existing type, the best-practice is to deprecate the old type and introduce a new type to replace it.
<!-- 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-specific patterns
Mainline is a project to allow updating subsystems ("mainline modules") of the Android OS individually, rather than updating the whole system image.
Mainline modules have to be "unbundled" from the core platform, which means all the interactions between each module and the rest of the world have to be done using formal (public or system) APIs.
There are certain design patterns mainline modules should follow. This section describes them.
The <Module>FrameworkInitializer pattern
If a mainline module needs to exposes @SystemService
classes (for example, JobScheduler
) then use the following pattern:
Expose a
<YourModule>FrameworkInitializer
class from your module. This class needs to be in$BOOTCLASSPATH
. Example: StatsFrameworkInitializerMark it with
@SystemApi(client = MODULE_LIBRARIES)
.Add a
public static void registerServiceWrappers()
method to it.Use
SystemServiceRegistry.registerContextAwareService()
to register a service manager class when it needs a reference to aContext
.Use
SystemServiceRegistry.registerStaticService()
to register a service manager class when it doesn't need a reference to aContext
.Call the
registerServiceWrappers()
method fromSystemServiceRegistry
's static initializer.
The <Module>ServiceManager pattern
Normally, in order to register system service binder objects or get references to them, one would use ServiceManager
, but mainline modules can't use it because it's hidden. This class is hidden because mainline modules aren't supposed to register or refer to system service binder objects exposed by the static platform or by other modules.
Mainline modules can use the following pattern instead to be able to register and get references to binder services that are implemented inside the module.
Create a
<YourModule>ServiceManager
class, following the design of TelephonyServiceManagerExpose the class as
@SystemApi
. If you only need to access it from$BOOTCLASSPATH
classes or system server classes, you can use@SystemApi(client = MODULE_LIBRARIES)
; otherwise@SystemApi(client = PRIVILEGED_APPS)
would work.This class would consists of:
- A hidden constructor, so only the static platform code can instantiate it.
- Public getter methods that return a
ServiceRegisterer
instance for a specific name. If you have one binder object, then you need one getter method. If you have two, then you need two getters. - In
ActivityThread.initializeMainlineModules()
, instantiate this class, and pass it to a static method exposed by your module. Normally, you add a static@SystemApi(client = MODULE_LIBRARIES)
API in yourFrameworkInitializer
class that takes it.
This pattern would prevent other mainline modules from accessing these APIs because there's no way for other modules to get an instance of <YourModule>ServiceManager
, even though the get()
and register()
APIs are visible to them.
Here is how telephony gets a reference to the telephony service: code search link .
If your implements a service binder object in native code, you use the AServiceManager
native APIs . These APIs correspond to the ServiceManager
Java APIs but the native ones are directly exposed to mainline modules. Don't use them to register or refer to binder objects that aren't owned by your module. If you expose a binder object from native, your <YourModule>ServiceManager.ServiceRegisterer
doesn't need a register()
method.
Permission definitions in Mainline modules
Mainline modules containing APKs may define (custom) permissions in their APK AndroidManifest.xml
in the same way as a regular APK.
If the defined permission is only used internally within a module, its permission name should be prefixed with the APK package name, for example:
<permission
android:name="com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER"
android:protectionLevel="signature" />
If the defined permission is to be provided as part of an updatable platform API to other apps, its permission name should be prefixed with "android.permission." (like any static platform permission) plus the module package name, to signal it's a platform API from a module while avoiding any naming conflicts, for example:
<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" />
Then the module can expose this permission name as an API constant in its API surface, for example HealthPermissions.READ_ACTIVE_CALORIES_BURNED
.