مفاهیم و پیادهسازی Jetpack Compose
از Android 3.0 (سطح API ۱۱) به بعد، خط لوله پردازش دوبعدی Android
از شتاب سختافزاری پشتیبانی میکند، یعنی همه عملیات طراحی که
روی بوم View انجام میشود از GPU استفاده میکند.
بهدلیل افزایش منابع موردنیاز برای فعال کردن شتاب سختافزاری،
برنامهتان RAM بیشتری مصرف خواهد کرد.
اگر سطح میانای برنامهسازی کاربردی هدف شما
>=14 باشد، شتاب سختافزاری بهطور پیشفرض فعال است، اما میتوان آن را بهطور صریح نیز فعال کرد. اگر برنامهتان فقط از نماهای استاندارد و Drawable استفاده میکند، روشن کردن آن بهصورت سراسری نباید هیچگونه اثر طراحی نامطلوبی ایجاد کند. بااینحال، چون شتاب سختافزاری برای همه عملیاتهای طراحی دوبعدی پشتیبانی نمیشود، روشن کردن آن ممکن است بر برخیاز نماهای سفارشی یا فراخوانیهای طراحی شما تأثیر بگذارد. مشکلات معمولاً بهصورت
عناصر نامرئی، استثناها، یا پیکسلهای اشتباه رندرشده ظاهر میشوند. برای رفع این مشکل،
Android به شما امکان میدهد شتاب سختافزاری را در
سطوح مختلف فعال یا غیرفعال کنید. کنترل شتاب سختافزاری را ببینید.
اگر برنامهتان طراحی سفارشی انجام میدهد، برنامهتان را در دستگاههای سختافزاری واقعی درحالیکه شتاب سختافزاری روشن است آزمایش کنید تا مشکلات را پیدا کنید. بخش پشتیبانی از عملیات طراحی مشکلات شناختهشده با شتاب سختافزاری و نحوه حل آنها را شرح میدهد.
همچنین OpenGL با میاناهای برنامهسازی کاربردی چارچوب و Renderscript را ببینید.
کنترل شتاب سختافزاری
میتوانید شتاب سختافزاری را در سطوح زیر کنترل کنید:
- برنامه
- فعالیت
- پنجره
- مشاهده
سطح برنامه
در فایل مانیفست Android، مشخصه زیر را به برچسب
<application> اضافه کنید تا شتاب سختافزاری برای کل
برنامه فعال شود:
<application android:hardwareAccelerated="true" ...>
سطح فعالیت
اگر برنامه شما با روشن بودن شتاب سختافزاری بهطور سراسری بهدرستی رفتار نمیکند، میتوانید آن را برای فعالیتهای فردی نیز کنترل کنید. برای فعال یا غیرفعال کردن شتاب سختافزاری در سطح فعالیت، میتوانید از مشخصه android:hardwareAccelerated برای عنصر <activity> استفاده کنید. مثال زیر شتاب سختافزاری را برای کل برنامه فعال میکند اما آن را برای یک فعالیت غیرفعال میکند:
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
سطح پنجره
اگر به کنترل دقیقتری نیاز دارید، میتوانید شتاب سختافزاری را برای پنجرهای خاص با کد زیر فعال کنید:
کاتلین
window.setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED )
جاوا
getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
مشاهده سطح
میتوانید شتاب سختافزاری را برای یک نمای خاص در زمان اجرا با کد زیر غیرفعال کنید:
کاتلین
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
جاوا
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
تعیین اینکه آیا نمای موردنظر شتاب سختافزاری دارد یا نه
گاهیاوقات برای برنامه مفید است که بداند آیا درحالحاضر شتاب سختافزاری دارد یا نه، بهویژه برای مواردی مثل نماهای سفارشی. این کار بهویژه زمانی مفید است که برنامه شما طراحی سفارشی زیادی انجام میدهد و همه عملیات بهدرستی توسط خط لوله رندرینگ جدید پشتیبانی نمیشوند.
دو روش مختلف برای بررسی اینکه آیا برنامه شتاب سختافزاری دارد یا نه وجود دارد:
View.isHardwareAcceleratedtrueرا برمیگرداند اگرViewبه پنجره شتاب سختافزاری متصل باشد.Canvas.isHardwareAcceleratedtrueرا برمیگرداند اگرCanvasشتاب سختافزاری داشته باشد.
اگر باید این بررسی را در کد ترسیم خود انجام دهید، درصورت امکان از
Canvas.isHardwareAccelerated بهجای
View.isHardwareAccelerated استفاده کنید. وقتی نمایی به پنجره شتابیافته سختافزاری پیوست میشود، همچنان میتوان آن را بااستفاده از «بوم» شتابنیافته سختافزاری ترسیم کرد. این اتفاق، برای مثال، هنگام ترسیم یک نما در یک
نقشه بیتی برای اهداف ذخیرهسازی موقت رخ میدهد.
مدلهای طراحی Android
وقتی شتاب سختافزاری فعال باشد، چارچوب Android از مدل طراحی جدیدی استفاده میکند که از فهرستهای نمایش برای ارائه برنامه شما در صفحه استفاده میکند. برای درک کامل فهرستهای نمایش و نحوه تأثیر آنها بر برنامهتان، مفید است که بدانید Android چگونه نماها را بدون شتاب سختافزاری نیز ترسیم میکند. بخشهای زیر مدلهای طراحی مبتنی بر نرمافزار و شتابیافته سختافزاری را شرح میدهند.
مدل طراحی نرمافزارمحور
در مدل ترسیم نرمافزار، نماها با دو مرحله زیر ترسیم میشوند:
- نامعتبر کردن سلسله مراتب
- ترسیم سلسلهمراتب
هرگاه برنامهای نیاز به بهروزرسانی بخشی از واسط کاربر خود داشته باشد،
invalidate() (یا یکی از انواع آن) را در هر نمایی که محتوای آن تغییر کرده است
فراخوانی میکند. پیامهای ابطال در سراسر سلسلهمراتب نما منتشر میشوند تا مناطقی از صفحهنمایش که باید دوباره ترسیم شوند (منطقه کثیف) محاسبه شوند. سپس سیستم Android هر نمایی را در سلسلهمراتب که با منطقه کثیف تلاقی دارد ترسیم میکند. متأسفانه این مدل طراحی دو اشکال دارد:
اول اینکه این مدل به اجرای کدهای زیادی در هر مرحله ترسیم نیاز دارد. برای مثال، اگر برنامه شما
invalidateرا روی دکمهای فراخوانی کند و آن دکمه روی نمای دیگری قرار داشته باشد، سیستم Android نما را دوباره ترسیم میکند، حتی اگر تغییری نکرده باشد.مشکل دوم این است که مدل طراحی میتواند اشکالهای موجود در برنامه شما را پنهان کند. ازآنجاییکه سیستم Android وقتی نماها با ناحیه کثیف تلاقی میکنند آنها را دوباره ترسیم میکند، نمایی که محتوای آن را تغییر دادهاید ممکن است دوباره ترسیم شود، حتی اگر
invalidateروی آن فراخوانی نشده باشد. وقتی این اتفاق میافتد، شما به نامعتبر شدن نمای دیگری برای بهدست آوردن رفتار مناسب متکی هستید. این رفتار میتواند هربار که برنامهتان را اصلاح میکنید تغییر کند. به همین دلیل، هرگاه دادهها یا وضعیتی را که بر کد ترسیم نما تأثیر میگذارد اصلاح میکنید، باید همیشهinvalidateرا در نماهای سفارشیتان فراخوانی کنید.
مدل طراحی با شتاب سختافزاری
سیستم Android همچنان از invalidate و draw برای درخواست
بهروزرسانیهای صفحه و برای رندر کردن نماها استفاده میکند، اما طراحی واقعی را بهطور متفاوتی مدیریت میکند.
سیستم Android بهجای اجرای فوری فرمانهای طراحی، آنها را در فهرستهای نمایش ضبط میکند که حاوی برونداد کد طراحی سلسلهمراتب نما است. بهینهسازی دیگر این است که سیستم Android فقط باید فهرستهای نمایش را برای نماهایی که با فراخوانی invalidate بهعنوان «کثیف» علامتگذاری شدهاند ضبط و بهروز کند. نماهایی که باطل نشدهاند را میتوان با صدور مجدد فهرست نمایش ضبطشده قبلی، دوباره ترسیم کرد. مدل جدید قرعهکشی
شامل سه مرحله است:
نامعتبر کردن سلسله مراتب
ضبط و بهروزرسانی فهرستهای نمایش
کشیدن فهرستهای نمایش
با این مدل، نمیتوانید به نمای متقاطع با ناحیه کثیف تکیه کنید تا
روش draw آن اجرا شود. برای اطمینان از اینکه سیستم Android فهرست نمایش نما را ضبط میکند، باید invalidate را فراخوانی کنید. فراموش کردن این کار
باعث میشود که نما حتی پساز تغییر، یکسان بهنظر برسد.
استفاده از فهرستهای نمایشی همچنین به عملکرد پویانمایی کمک میکند زیرا تنظیم ویژگیهای خاص، مانند آلفا یا چرخش، نیازی به باطل کردن نمای هدف ندارد (این کار بهطور خودکار انجام میشود). این بهینهسازی همچنین برای نماهای دارای فهرستهای نمایش (هر نمایی که برنامه شما شتاب سختافزاری دارد) اعمال میشود. برای مثال، فرض کنید LinearLayout وجود دارد که حاوی
ListView در بالای Button است. فهرست نمایش برای
LinearLayout به این شکل است:
DrawDisplayList(ListView)DrawDisplayList(Button)
فرض کنید اکنون میخواهید میزان ماتی ListView را تغییر دهید. پساز
فراخوانی setAlpha(0.5f) در ListView، فهرست نمایش اکنون
شامل این مورد است:
SaveLayerAlpha(0.5)DrawDisplayList(ListView)RestoreDrawDisplayList(Button)
کد طراحی پیچیده ListView اجرا نشد. درعوض،
سیستم فقط فهرست نمایش LinearLayout بسیار سادهتر را بهروز کرد.
در برنامهای که شتاب سختافزاری آن فعال نیست، کد ترسیم
هم فهرست و هم والد آن دوباره اجرا میشود.
پشتیبانی از عملیات طراحی
وقتی سختافزار شتابدهی میشود، خط لوله پردازش دوبعدی از رایجترین عملیاتهای طراحی Canvas و همچنین بسیاری از عملیاتهای کمتر استفادهشده پشتیبانی میکند. همه عملیات طراحی که برای ارائه برنامههایی که با Android، ابزارهها و چیدمانهای پیشفرض، و جلوههای دیداری پیشرفته رایج مانند بازتابها و بافتهای کاشیکاریشده ارسال میشوند، پشتیبانی میشوند.
جدول زیر سطح پشتیبانی از عملیات مختلف را در سطوح API شرح میدهد:
| اولین سطح میانای برنامهسازی کاربردی پشتیبانیشده | ||||
| Canvas | ||||
| drawBitmapMesh() (colors array) | 18 | |||
| drawPicture() | 23 | |||
| drawPosText() | 16 | |||
| drawTextOnPath() | 16 | |||
| drawVertices() | 29 | |||
| setDrawFilter() | 16 | |||
| clipPath() | 18 | |||
| clipRegion() | 18 | |||
| clipRect(Region.Op.XOR) | 18 | |||
| clipRect(Region.Op.Difference) | 18 | |||
| clipRect(Region.Op.ReverseDifference) | 18 | |||
| clipRect() با چرخش/پرسپکتیو | 18 | |||
| رنگ | ||||
| setAntiAlias() (برای نوشتار) | 18 | |||
| setAntiAlias() (برای خطوط) | 16 | |||
| setFilterBitmap() | 17 | |||
| setLinearText() | ✗ | |||
| setMaskFilter() | ✗ | |||
| setPathEffect() (برای خطوط) | 28 | |||
| setShadowLayer() (other than text) | 28 | |||
| setStrokeCap() (برای خطوط) | 18 | |||
| setStrokeCap() (برای نقاط) | 19 | |||
| setSubpixelText() | 28 | |||
| Xfermode | ||||
| PorterDuff.Mode.DARKEN (میانگیر قاب) | 28 | |||
| PorterDuff.Mode.LIGHTEN (میانگیر قاب) | 28 | |||
| PorterDuff.Mode.OVERLAY (بافر قاب) | 28 | |||
| سایهزن | ||||
| ComposeShader inside ComposeShader | 28 | |||
| سایهزنهای همنوع در ComposeShader | 28 | |||
| ماتریس محلی در ComposeShader | 18 | |||
مقیاسبندی بوم نقاشی
خط لوله پردازش دوبعدی با شتاب سختافزاری ابتدا برای پشتیبانی از طراحی بدون مقیاس ساخته شد، اما برخی عملیات طراحی در مقادیر مقیاس بالاتر کیفیت را بهطور قابلتوجهی کاهش میدادند. این عملیات بهعنوان بافتهایی که در مقیاس ۱.۰ ترسیم شدهاند و توسط GPU تبدیل شدهاند، اجرا میشوند. از سطح API ۲۸، همه عملیاتهای طراحی میتوانند بدون مشکل مقیاسبندی شوند.
جدول زیر نشان میدهد که چه زمانی پیادهسازی برای مدیریت صحیح مقیاسهای بزرگ تغییر کرده است:
| عملیات طراحی که باید مقیاسبندی شود | اولین سطح میانای برنامهسازی کاربردی پشتیبانیشده |
| drawText() | 18 |
| drawPosText() | 28 |
| drawTextOnPath() | 28 |
| شکلهای ساده | 17 |
| شکلهای پیچیده | 28 |
| drawPath() | 28 |
| لایه سایه | 28 |
اگر برنامه شما تحتتأثیر هرکدام از این ویژگیهای مفقود یا محدودیتها قرار گرفته است،
میتوانید شتاب سختافزاری را فقط برای بخش تحتتأثیر قرارگرفته برنامهتان
با فراخوانی setLayerType(View.LAYER_TYPE_SOFTWARE, null) خاموش کنید.
بهاینترتیب، همچنان میتوانید از مزایای شتاب سختافزاری در جاهای دیگر بهرهمند شوید.
برای اطلاعات بیشتر درباره نحوه فعال و غیرفعال کردن شتاب سختافزاری در سطوح مختلف در برنامهتان، به کنترل شتاب سختافزاری مراجعه کنید.
مشاهده لایهها
در همه نسخههای Android، نماها توانایی پرداز کردن در میانگیرهای خارج از صفحه را داشتهاند، چه بااستفاده از حافظه پنهان رسم نما، چه بااستفاده از Canvas.saveLayer. میانگیرها یا لایههای خارج از صفحه کاربردهای متعددی دارند. میتوانید از آنها برای دریافت عملکرد بهتر هنگام پویانمایی نماهای پیچیده یا اعمال جلوههای ترکیببندی استفاده کنید. برای مثال، میتوانید جلوههای محو شدن را بااستفاده از
Canvas.saveLayer پیادهسازی کنید تا بهطور موقت نمایی را در لایهای پرداز کنید و سپس آن را
با ضریب شفافیت به صفحه برگردانید.
از Android 3.0 (سطح میانای برنامهسازی کاربردی ۱۱) به بعد، کنترل بیشتری بر نحوه و زمان استفاده از لایهها با روش View.setLayerType دارید. این میانای برنامهسازی کاربردی دو پارامتر میگیرد: نوع لایهای که میخواهید استفاده کنید و یک شیء اختیاری Paint که نحوه ترکیببندی لایه را توصیف میکند. میتوانید از پارامتر
Paint برای اعمال فیلترهای رنگ، حالتهای ترکیب ویژه، یا
کدری به لایه استفاده کنید. نما میتواند از یکی از سه نوع لایه استفاده کند:
LAYER_TYPE_NONE: نما بهطور عادی ارائه میشود و با میانگیر خارج از صفحه پشتیبانی نمیشود. این رفتار پیشفرض است.LAYER_TYPE_HARDWARE: اگر برنامه شتاب سختافزاری داشته باشد، نما در سختافزار بهصورت بافت سختافزاری رندر میشود. اگر برنامه شتاب سختافزاری نداشته باشد، این نوع لایه مانندLAYER_TYPE_SOFTWAREرفتار میکند.
LAYER_TYPE_SOFTWARE: نما در نرمافزار بهصورت نقشه بیتی پرداز میشود.
نوع لایهای که استفاده میکنید به هدفتان بستگی دارد:
عملکرد: از نوع لایه سختافزاری برای رندر کردن نما در بافت سختافزاری استفاده کنید. وقتی نمایی در لایهای رندر میشود، کد طراحی آن تا زمانی که نما
invalidateرا فراخوانی نکند، لازم نیست اجرا شود. برخیاز پویانماییها، مثل پویانماییهای آلفا، میتوانند مستقیماً روی لایه اعمال شوند که برای GPU بسیار کارآمد است.جلوههای دیداری: از نوع لایه سختافزاری یا نرمافزاری و
Paintبرای اعمال پردازشهای دیداری خاص به نما استفاده کنید. برای مثال، میتوانید بااستفاده ازColorMatrixColorFilter، نمایی را بهصورت سیاه و سفید بکشید.سازگاری: از نوع لایه نرمافزار استفاده کنید تا نما را مجبور کنید در نرمافزار پرداز شود. اگر نمایی که شتاب سختافزاری دارد (برای مثال، اگر کل برنامه شما شتاب سختافزاری دارد) مشکل پردازشی دارد، این روشی آسان برای دور زدن محدودیتهای خط لوله پردازش سختافزاری است.
مشاهده لایهها و پویانماییها
وقتی برنامهتان شتاب سختافزاری داشته باشد، لایههای سختافزاری میتوانند پویانماییهای سریعتر و روانتری ارائه دهند. اجرای پویانمایی با سرعت ۶۰ فریم در ثانیه همیشه هنگام پویانمایی نماهای پیچیده که عملیات طراحی زیادی را صادر میکنند، امکانپذیر نیست. این مشکل را میتوان بااستفاده از لایههای سختافزاری برای رندر کردن نما
به بافت سختافزاری کاهش داد. سپس میتوان از بافت سختافزار برای پویانمایی نما استفاده کرد و نیاز به بازترسیم مداوم نما درطول پویانمایی را ازبین برد. نما دوباره ترسیم نمیشود مگراینکه ویژگیهای نما را تغییر دهید،
که invalidate را فرامیخواند، یا اگر invalidate را بهصورت دستی فرابخوانید. اگر
پویانماییای را در برنامهتان اجرا میکنید و به نتایج روانی که میخواهید نمیرسید،
فعال کردن لایههای سختافزاری را در نماهای پویانماییشدهتان درنظر بگیرید.
وقتی نمایشی توسط لایه سختافزاری پشتیبانی میشود، برخیاز ویژگیهای آن توسط نحوه ترکیب لایه روی صفحهنمایش مدیریت میشود. تنظیم این ویژگیها کارآمد خواهد بود زیرا نیازی به باطل کردن و بازترسیم نما ندارند. در زیر فهرستی از ویژگیهایی که بر نحوه ترکیببندی لایه تأثیر میگذارند آورده شده است. فراخوانی تنظیمکننده برای هریک از این ویژگیها منجر به باطلسازی بهینه و عدم بازترسیم نمای هدف میشود:
alpha: ماتی لایه را تغییر میدهد
x،y،translationX،translationY: جای لایه را تغییر میدهد
scaleX،scaleY: اندازه لایه را تغییر میدهد
rotation،rotationX،rotationY: جهت لایه را در فضای سهبعدی تغییر میدهد
pivotX،pivotY: مبدأ تبدیلهای لایه را تغییر میدهد
این ویژگیها نامهایی هستند که هنگام پویانمایی کردن نما با
ObjectAnimator استفاده میشوند. اگر میخواهید به این ویژگیها دسترسی داشته باشید،
تنظیمکننده یا دریافتکننده مناسب را فراخوانی کنید. برای مثال، برای اصلاح ویژگی آلفا،
setAlpha را فراخوانی کنید. تکهکد زیر کارآمدترین روش را برای چرخاندن نما بهصورت سهبعدی حول محور Y نشان میدهد:
کاتلین
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).start()
جاوا
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start();
ازآنجاییکه لایههای سختافزاری حافظه ویدیو را مصرف میکنند، اکیداً توصیه میشود که آنها را فقط برای مدت پویانمایی فعال کنید و سپس پساز اتمام پویانمایی آنها را غیرفعال کنید. میتوانید این کار را بااستفاده از شنوندگان پویانمایی انجام دهید:
کاتلین
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { view.setLayerType(View.LAYER_TYPE_NONE, null) } }) start() }
جاوا
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); animator.start();
برای اطلاعات بیشتر درباره پویانمایی ویژگی، به پویانمایی ویژگی مراجعه کنید.
نکتهها و ترفندها
رفتن به گرافیک دوبعدی با شتاب سختافزاری میتواند فوراً عملکرد را افزایش دهد، اما همچنان باید برنامهتان را طوری طراحی کنید که با دنبال کردن این توصیهها بهطور مؤثر از GPU استفاده کند:
- تعداد نماها را در برنامهتان کاهش دهید
- هرچه سیستم نماهای بیشتری برای ترسیم داشته باشد، سرعت آن کمتر خواهد بود. این امر برای خط لوله پردازش نرمافزار نیز اعمال میشود. کاهش نماها یکی از سادهترین راهها برای بهینهسازی میانای کاربر است.
- اجتناب از برداشت اضافی
- لایههای زیادی را روی هم نکشید. هر نمایی را که کاملاً توسط نماهای مات دیگر در بالای آن پنهان شده است بردارید. اگر لازم است چند لایه را بهصورت ترکیبشده روی هم بکشید، آنها را در یک لایه ادغام کنید. یک قانون سرانگشتی خوب با سختافزار فعلی این است که بیشاز ۲٫۵ برابر تعداد پیکسلهای روی صفحه در هر فریم ترسیم نشود (پیکسلهای شفاف در بیتمپ حساب میشوند!).
- Don't create render objects in draw methods
- یک اشتباه رایج این است که هر بار که یک روش پردازش فراخوانی میشود، یک
Paintجدید یا یکPathجدید ایجاد شود. این کار باعث میشود زبالهروب بیشتر اجرا شود و همچنین حافظههای پنهان و بهینهسازیها را در خط لوله سختافزار دور میزند. - شکلها را خیلی زیاد اصلاح نکنید
- برای مثال، شکلها، مسیرها، و دایرههای پیچیده بااستفاده از ماسکهای بافت رندر میشوند. هربار که مسیری را ایجاد یا اصلاح میکنید، خط لوله سختافزار ماسک جدیدی ایجاد میکند که میتواند پرهزینه باشد.
- نقشههای بیتی را زیاد تغییر ندهید
- هر بار که محتوای یک نقشه بیتی را تغییر میدهید، دفعه بعد که آن را ترسیم میکنید، دوباره بهعنوان بافت GPU بارگذاری میشود.
- بااحتیاط از نسخه آلفا استفاده کنید
- وقتی بااستفاده از
setAlpha،AlphaAnimation، یاObjectAnimatorنمایی را نیمهشفاف میکنید، این نما در میانگیر خارج از صفحه رندر میشود که نرخ پر کردن موردنیاز را دوبرابر میکند. هنگام اعمال آلفا در نماهای بسیار بزرگ، نوع لایه نما را رویLAYER_TYPE_HARDWAREتنظیم کنید.