کلاس R اندروید

آشنایی کامل با کلاس R اندروید

اگر کمی در برنامه نویسی اندروید تجربه داشته باشید، حتما با یک کلاس به نام R در اندروید برخورد کرده اید. تقریبا میشود گفت به هر قسمتی از اپلیکیشن اندروید سر بزنید، کلاس R اندروید هم آنجا حضور دارد. مثلا وقتی که میخواهیم id یک المان را از داخل فایل layout پیدا کنیم، باید از کلاس R اندروید استفاده کنیم. اما این کلاس مرموز چیست و چه وظیفه ای در برنامه نویسی اندروید دارد؟ در این نوشته از بلاگ برنامه چی میخواهیم این کلاس را کاملا بررسی کنیم. با ما همراه باشید.

کلاس R اندروید

اگر حوصله خواندن کامل متن را ندارید، کلاس R اندروید وظیفه نگهداری و مدیریت کردن دسترسی به این منابع در اندروید را دارد. منابع به همه فایل های غیر کدی که در اپلیکیشن استفاده میکنیم گفته میشود، مانند تصاویر، متن ها، فایل های Style و غیره. بصورت خلاصه در این نوشته موضوعاتی مانند تعریف کلاس R، نحوه استفاده از کلاس R، تاریخچه این کلاس و کاربرد هایی که در برنامه نویسی اندروید دارد صحبت کردیم. فهرست تیترهای این مقاله را میتوانید در ادامه مشاهده کنید.


تعریف: کلاس R در اندروید چیست؟

هر اپلیکیشن اندروید شامل یک سری منابع است. در برنامه نویسی به تصاویر، ویدئو ها، رشته های متنی، آیکون ها، رنگ ها و همه مواردی که غیر از کد، برای ساخت اپلیکیشن از آنها استفاده میکنیم، منبع گفته میشود. همه این منابع در یک کلاس به نام کلاس R اندروید لیست میشوند. حتما تا الان حدس زده اید که اسم کلاس R، اولین حرف از کلمه Resources می باشد. یعنی اینجا محلی است که میتوانیم همه منابع اندروید را پیدا کنیم. هر اپلیکیشن بومی در اندروید با استفاده از مکانیزم های خاصی که در سیستم عامل اندروید وجود دارد، به این منابع دسترسی دارند. در حقیقت این دسترسی ها بر مبنای لیست ID هایی که در کلاس R اندروید موجود هستند اتفاق می افتد.

پس یعنی کلاس R اندروید شامل یک سری اطلاعات درباره منابع اپلیکیشن ما است. این کلاس بصورت اتوماتیک  توسط Andoroid Asset Packaging Tool یا AAPT تولید میشود و نیازی نیست که خودتان آن را بسازید. از زمانی که پروژه اولیه برای ساخت اپلیکیشن خود را شروع میکنید، این کلاس در دسترس و قابل استفاده است.

منابع در کلاس R اندروید

هنگامی که شما المان هایی را در یکی از فایل های Layout مانند فایل activity_main.xml اضافه میکنید، id این المان ها بصورت خودکار به فایل R اندروید اضافه میشود. همه منابع در اندروید دارای id یگانه و خاص خود هستند. شما میتوانید از این id ها برای دریافت منبع های مختلف در کلاس جاوا اکتیویتی استفاده کنید و همه کارهایی که میخواهید را با این المان ها انجام بدهید. دقت کنید که برای هر نوع مختلف از منابع، یک کلاس تو در تو (Nested) وجود دارد. مثلا برای رشته ها، کلاس R.string را باید استفاده کنیم. سپس برای هر منبعی از این نوع، باید از id خاص همان منبع مورد نظر را نام ببریم. مثلا R.string.app_name یک منبع خاص از نوع String و به نام app_name را برمیگرداند.

نکته مهمی که باید بدانید این است که مقدار همه id ها در اندروید، از نوع Integer هستند. یعنی هرچند ممکن است برای یک المان، خودمان یک id از نوع رشته، مثلا app_name را تعریف کنیم. اما به ازای هرکدام از این id ها، یک عدد در کلاس R اندروید ذخیره میشود.

منابع اپلیکیشن اندروید در ساختار فولدر های یک پروژه، درون فایل res قرار میگیرند (این فولدر را با فولدر resources در برنامه های جاوا اشتباه نگیرید). این منابع میتوانند فرمت های متفاوتی داشته باشند. icon ها و layout ها در فولدرهایی با همین نام ها ریخته میشوند. رشته های متنی (معمولا) همگی درون یک فایل به اسم strings.xml ذخیره خواهند شد.

این مکانیزم بازخوانی و دریافت منابع بصورت خیلی گسترده و سنگین در همه قسمت های Android SDK به کار برده میشود و به همین دلیل، این مکانیزم برای کارایی بالا بهینه شده است. اما متاسفانه اصلا برای شادی و ایمنی برنامه نویس ها بهینه نشده است (:دی). حالا ببینیم کلاس R اندروید چگونه تولید شده و توسط بقیه اپلیکیشن استفاده میشود.


نحوه تولید کلاس R اندروید

از زمانی که اندروید در سال 2008 معرفی شد، مکانیزم دریافت منابع تا امروز تغییر نکرده است. روند کار به این صورت است: قبل از اینکه کدهای اپلیکیشن کامپایل شوند، همه منابعی که درون فولدر res وجود دارند باید پیدا شوند. بعد از این مرحله، کدهای کلاس R اندروید تولید شده و به بقیه کدهای اپلیکیشن اضافه میشوند. وقتی که این کار انجام شد، کدهای اپلیکیشن ما میتوانند ID های منابع را از طریق کلاس R بازخوانی کرده و استفاده کند.

id ها در کلاس R اندروید

در این زمان پلاگین Android Development Tools یا ADT وارد عمل میشود و AAPT را اجرا میکند، فایل R.java را تولید میکند، کدهای آن را به بقیه اپلیکیشن می چسباند و در نهایت همه این کدها را با هم کامپایل میکند.

public final class R {
    public static final class string {
        public static final int cancel = 17039360;
        public static final int copy = 17039361;
        public static final int cut = 17039363;
        public static final int no = 17039369;
        public static final int ok = 17039370;
        public static final int paste = 17039371;
        public static final int yes = 17039379;
    }

    public static final class layout {
        public static final int activity_list_item = 17367040;
        public static final int list_content = 17367060;
        public static final int preference_category = 17367042;
        public static final int select_dialog_item = 17367057;
    }

    public static final class drawable {
        public static final int btn_default = 17301508;
        public static final int btn_dialog = 17301527;
        public static final int btn_dropdown = 17301510;
        public static final int btn_minus = 17301511;
        public static final int btn_plus = 17301512;
        public static final int checkbox_off_background = 17301519;
        public static final int checkbox_on_background = 17301520;
        public static final int divider_horizontal_bright = 17301522;
    }
}

اولین ورژن ADT همه ID ها را بصورت ثابت در کلاس R تعریف میکرد (یعنی با استفاده از public static final int). این کار برای اپلیکیشن های ماژولاری که دارای چند لایبرری مختلف بودند مشکلات زیادی درست میکرد. یعنی مقدار ID های این ماژول ها ممکن بود با هم یکسان شده و در نتیجه همه ماژول های کتابخانه باید دوباره کامپایل میشدند. برای حل این مشکل، بعد از ADT ورژن 14، کلاس R مربوط به ماژول های لایبرری، فیلد های خودش را با public static int تعریف میکند (یعنی دیگه ثابت نیست). اما فیلد های مربوط به ماژول های خود اپلیکیشن بصورت ثابت تعریف میشوند. با این راهکار، فیلد های اپلیکیشن همیشه ثابت می ماند و مقدارهای دیگری برای ماژول های لایبرری تولید خواهد شد.

البته این تغییرات در آن زمان باعث به وجود آمدن مشکلات دیگری شد. زیرا دستور Switch در جاوا نیاز به دسترسی به ثابت ها در زمان کامپایل دارد. یعنی این دستور نمیتواند از ID های ماژول لایبرری به عنوان Statement Value استفاده کند. اما در زبان کاتلین و دستور when، دیگر این مشکل وجود ندارد.


ظهور Gradle

gradle در کلاس R اندروید

در کنفرانس Google I/O سال 2014 بود که Android Studio معرفی شد. این محیط برنامه نویسی مبتنی بر Gradle کار میکرد و جایگزین ADT و سیستم Ant-Based آن شد. اما به دلیل اینکه تولید کلاس R اندروید هنوز هم توسط aapt انجام میشود، تغییرات زیادی در نحوه تولید و استفاده از ID ها ایجاد نشد.


مشکل بزرگ کلاس R

یکی از مشکلات بزرگی که وجود داشت این بود که با رشد پروژه، فایل کلاس R هم طبیعتا باید رشد کند. اما تقریبا سال 2017 بود که تیم های توسعه بزرگ متوجه شدند که کلاس R با یک سرعت بسیار زیاد رشد میکند. مثلا Elin Nilsson در سخنرانی که درباره اپلیکیشن های ماژولار داشت گفت در پروژه آنها که 1.2 میلیون خط کد داشت، فایل های کلاس R، روی هم 56 میلیون خط کد تولید کرده بودند!

اما آیا واقعا در این پروژه 56 میلیون منبع وجود داشت؟ عمرا! دلیل اصلی به وجود آمدن این فایل های سنگین، این بود که مقادیر کپی درون آنها تولید شده بود. در حقیقت کلاس R برای هر ماژول اپلیکیشن شما بصورت جداگانه تولید میشود و هرکدام از این فایل ها ارجاع هایی به همه فایل های دیگر R که با آن در ارتباط هستند را نگهداری میکند. به شکل زیر دقت کنید:

دیاگرام کلاس R اندروید

در این مثال، کتابخانه MDC شامل کلاس com.google.android.material.R به همراه ارجاع به منابع material است. ماژول lib ما به ماژول کتابخانه MDC تکیه دارد و شامل تعدادی منابع دیگر است. همچنین این ماژول کلاس com.exmaple.myapp.lib.R مخصوص خودش را دارد که ID منابع خودش و ID منابع مربوط به کتابخانه MDC درون آن است.

در نهایت، ماژول اپ شامل کلاس com.example.myapp.R مخصوص خودش است و همه منابع ماژول lib و کتابخانه MDC را مجددا درون خودش نگه میدارد. یعنی ID منابع Material Components (همون آخری) سه بار تولید میشود! به همین دلیل یک پروژه کوچک گاهی میتواند یک کلاس R با میلیون ها خط کد تولید کند.


راه نجات

راه نجات کلاس R اندروید

تیم توسعه اندروید متوجه این مشکل شدند و تعدادی راه حل برای رفع این مشکل بزرگ ارائه کردند. در ادامه لیست این راه حل ها در هر نسخه ای که منتشر شده را با هم مشاهده میکنیم:


1- Android Gradle Plugin 3.3

 در این ورژن یک flag معرفی شد که داکیومنتیشن زیادی درباره آن وجود نداشت ولی میتوانید آن را درون gradle.properties فعال کنید:

android.namespacedRClass=true

با فعال کردن این گزینه، کلاس های R برای هر کتابخانه فقط منابع خود را نگهداری میکنند و منابع دیگر کتابخانه ها درون آنها کپی نمیشود. این راه حل تاثیر بسیار زیادی بر حجم کلاس R و سریع تر شدن build ها دارد.

علاوه بر اینها، این کار باعث میشود ماژول های نرم افزار از دیگر ماژول ها ایزوله شوند که از لحاظ معماری نرم افزار، نکته مثبتی است. در حقیقت تا زمانی که درون کلاس R یک ایمپورت صریح به کلاس R یک ماژول دیگر وجود نداشته باشد، ماژول فقط از منابع خودش استفاده میکند. یعنی این کار به ماژول اجازه استفاده از یک Drawable یا String از یک ماژول دیگر را نمیدهد.

اما متاسفانه اندروید استودیو بصورت اتوماتیک و در زمان توسعه، برای این کار اروری نشان نمیدهد. فقط در زمان کامپایل کردن به شما میگوید که در کدهای شما اشتباه وجود دارد.

خب این راه حل بسیار خوبی است. اما در نهایت باز هم کلاس R یک لیست از int های مختلف است که هنوز هم باید آن را کامپایل کنیم. اگر میتوانستیم مستقیما فایل bytecode جاوا را تولید کنیم کار ما خیلی ساده تر میشد. با استفاده از این flag در gradle.properties میتوانیم این کار را انجام بدهیم:

android.enableSeparateRClassCompilation=true

این کار به AGP (Android Gradle Plugin) دستور میدهد که فایل های کلاس R را به شکل فایل های jar کامپایل شده تولید کند و بصورت اتوماتیک آنها را به بقیه پروژه بچسباند. در حقیقت با  فعال کردن این قابلیت، به جای تولید R.java در میسر build/generated، فایل R.jar در مسیر build/intermediate در فرآیند ساخت پروژه تولید میشود.

شاید این کار سرعت build شدن پروژه را خیلی سریع تر نکند، اما باز هم از هیچی بهتر است. البته شما میتوانید هرکدام از این پرچم ها را بصورت جداگانه و بصورت دلخواه فعال کنید.


2- Android Gradle Plugin 3.4

این نسخه زیاد تفاوتی با نسخه قبلی نداشت اما یک پاراگراف جالب در قسمت توضیحات انتشار آن نوشته شده بود:

استفاده درست از نام های یکتا برای پیکیج در حال حاضر اجباری نیست اما در نسخه های بعدی این پلاگین قوانین سختگیرانه تری خواهد داشت. در پلاگین نسخه 3.4 شما میتوانید با اضافه کردن این خط به gradle.properties بررسی کنید که نام های مناسبی برای پکیج ها انتخاب کرده اید یا نه:

android.uniquePackageNames=true

در حال حاضر اطلاعات بیشتری برای این راه حل در دسترس نیست اما داشتن یک نام پکیج خاص که فقط و فقط در یک ماژول پروژه استفاده شده است به نظر ایده خوبی است. به نظرم بهتر است این بررسی را همین الان انجام بدهیم تا برای تغییرات قوانین آینده هم به دردسر خاصی برخورد نکنیم.


3- Android Gradle Plugin 3.6

این نسخه قوانین اشاره شده در مورد قبل، یعنی استفاده از نام های یکتا برای هر پکیج را پیاده سازی کرده بود. بعد از این ورژن، AGP مسیر کامپایل کردن پروژه را ساده تر کرد و برای هر ماژول فقط یک کلاس R تولید میشود تا روند build سریع تر شود.

ورژن 3.6 این پلاگین پرچم android.enableSeparateRClassCompilation را بصورت پیش فرض فعال کرده است و شما اجازه ندارید آن را غیرفعال کنید.

علاوه بر اینها یک flag جدید به نام android.enableAppCompileTimeRClass بصورت آزمایشی در این نسخه معرفی شده است. این پرچم تنها محدود به ماژول های اپلیکیشن است. در حالت عادی، قبل از اینکه فاز کامپایل یک ماژول اپلیکیشن بتواند شروع شود، همه کلاس های R از تمامی ماژول های دیگر باید دوباره تولید شوند تا یک مجموعه از ID های یکتا و نهایی ساخته شوند که در زمان اجرا توسط ماژول اپلیکیشن مورد استفاده قرار میگیرند. این پرچم آزمایشی مشکلی که گفتیم را با ساختن یک کلاس R تقلبی مخصوص ماژول اپلیکیشن حل میکند. در حالی که بعد از ساختن آن، مقادیر واقعی ID را به آن اضافه میکند.

یکی از محدودیت های این روش این است که ID منابع مربوط به ماژول اپلیکیشن دیگر نمیتوانند final باشند (مانند مقادیر مربوط به ماژول لایبرری). بنابراین همانطور که گفته شد نمیتوانیم از آنها در دستور switch یا به عنوان پارامتر Annotation استفاده کنیم.


4- Android Gradle Plugin 4.1

این نسخه از AGP هم بصورت پیشفرض قوانین نام گذاری کلاس R را فعال کرده است. در این نسخه پرچم زیر:

android.namespacedRClass=true

تغییر نام داده شده و به اسم زیر قابل دسترس است:

android.nonTransitiveRClass=true

همچنین یک flag مشابه هم برای ماژول اپلیکیشن وجود دارد.

android.experimental.nonTransitiveAppRClass=true

با فعال کردن این ویژگی دقیقا همان فعالیت هایی که در قسمت قبلی توضیح داده شد برای تولید کلاس R انجام میشود. یعنی هر ماژول فقط منابع خود را در کلاس R خودش ذخیره میکند و از منابع دیگر ماژول ها استفاده نمیکند.


 سوالی دارید؟

در این نوشته درباره کلاس R اندروید، تاریخچه و روند تولید آن در سیستم عامل صحبت کردیم. اگر هنوز در این باره سوالی دارید یا نکته ای از قلم افتاده، میتوانید با نوشتن آن در قسمت نظرات، این مقاله آموزشی را کامل تر کنید.


منابع بیشتر برای مطالعه

میتوانید از منابع زیر برای مطالعه بیشتر در باره کلاس R اندروید استفاده کنید:

2 دیدگاه دربارهٔ «آشنایی کامل با کلاس R اندروید»

  1. اندروید ۱۰ یه حالتی داشت وقتی گوشی تو جیب بود و درش میاوردی بدون اینکه صفحه رو لمس کنی اتوماتیک ساعت رو بصورت کامل حتی ثانیه شمارش هم نشون میداد ولی اندروید ۱۱ ظاهرا این آپشن رو نداره!!! لطفا اگه از تنظیماتش اطلاع دارید راهنمایی کنید

    1. سلام و خسته نباشید خدمت شما دوست عزیز.
      میتونین این مورد رو توی تنظیمات شخصی سازی گوشیتون نگاه کنین. البته شاید برند های متفاوت گوشی، شخصی سازی های متفاوتی رو ارائه بدن.

دیدگاه‌ خود را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

اسکرول به بالا