<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>اندروید استودیو</title>
	<atom:link href="https://android-studio.ir/feed/" rel="self" type="application/rss+xml" />
	<link>https://android-studio.ir/</link>
	<description>آموزش برنامه نویسی اندروید</description>
	<lastBuildDate>Thu, 13 Apr 2023 12:23:37 +0000</lastBuildDate>
	<language>fa-IR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>کار با امولاتور و ساخت دیوایس مجازی (AVD)</title>
		<link>https://android-studio.ir/android-emulator-avd/</link>
					<comments>https://android-studio.ir/android-emulator-avd/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 12 Jul 2022 06:14:38 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<category><![CDATA[دانلود فایل‌ها و ابزار]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=202645</guid>

					<description><![CDATA[<p>تست و دیباگ پروژه یکی از مهم‌ترین فرایندهای توسعه‌ی اپلیکیشن اندرویدی به شمار می‌رود. در این فرایند پروژه روی یک دستگاه حقیقی یا مجازی اندرویدی اجرا می‌شود تا توسعه‌دهنده بتواند کارکرد کدهای خود را بررسی کرده و نواقص احتمالی را برطرف کند. در این آموزش قصد دارم نحوه‌ی کار با شبیه ساز (امولاتور) اندروید استودیو [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/android-emulator-avd/">کار با امولاتور و ساخت دیوایس مجازی (AVD)</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/android_emulator_avd_tutorial.png" alt="آموزش نصب امولاتور (شبیه ساز) اندروید استودیو و AVD" /></p>
<p>تست و دیباگ پروژه یکی از مهم‌ترین فرایندهای توسعه‌ی اپلیکیشن اندرویدی به شمار می‌رود. در این فرایند پروژه روی یک دستگاه حقیقی یا مجازی اندرویدی اجرا می‌شود تا توسعه‌دهنده بتواند کارکرد کدهای خود را بررسی کرده و نواقص احتمالی را برطرف کند.<br />
در این آموزش قصد دارم نحوه‌ی کار با شبیه ساز (امولاتور) اندروید استودیو و همچنین اضافه کردن و اجرای دیوایس‌ها از طریق AVD را بررسی کنم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#what-is-android-emulator">شبیه ساز یا Emulator چیست؟</a></li>
<li><a href="#avd-vs-other-android-emulators">چرا AVD؟</a></li>
<li><a href="#install-android-studio-emulator-avd">نصب امولاتور اندروید استودیو</a></li>
<li><a href="#download-avd-system-images">دانلود System Image ها</a></li>
</ul>
</div>
<h2 id="what-is-android-emulator">شبیه ساز یا Emulator چیست؟</h2>
<p>به نام خدا. در زمان نگارش این مبحث آموزشی، اندروید ۱۲ به عنوان آخرین نسخه‌ی پایدار این سیستم‌عامل منتشر شده و حدود ۱۲% از کل دیوایس‌های اندرویدی را پوشش می‌دهد. این یعنی هنوز درصد قبل توجهی از موبایل‌ها، تبلت‌ها و دستگاه‌های اندرویدی دیگر از نسخه‌های پایین‌تر استفاده می‌کنند.<br />
بنابراین شما به عنوان یک برنامه نویس و توسعه دهنده‌ی اندروید لازم است پروژه‌ی اندرویدی خود را روی چندین دستگاه اندرویدی با نسخه‌های متفاوت تست کنید تا مطمئن شوید اپلیکیشن روی اکثر دستگاه‌هایی که در اختیار مردم قرار دارد عملکرد صحیحی خواهد داشت.<br />
به عبارت دیگر شما باید چندین گوشی و تبلت خریداری کنید تا بتوانید پروژه را روی تک تک آنها بررسی کنید که علاوه بر افزایش هزینه، چالش‌های خاص خود را به همراه دارد.<br />
اینجاست که امولاتور یا شبیه سازها وارد میدان شده و کار را بسیار ساده‌تر می‌کنند. با استفاده از شبیه ساز می‌توان هر تعداد از دیوایس‌های مجازی اندرویدی را روی کامپیوتر نصب و اجرا کرد. یعنی دیگر خبری از دیوایس فیزیکی و مشکلات اتصال آن به اندروید استودیو نیست.<br />
با استفاده از امولاتور به راحتی می‌توانید چندین دیوایس مجازی با نسخه‌ی سیستم عامل و اندازه‌ی صفحه نمایش مختلف ایجاد و پروژه‌ی خود را روی آنها تست و دیباگ کنید.<br />
شبیه سازهای مختلفی برای اندروید ساخته شده که روی سیستم‌عامل‌های محبوب مانند ویندوز، لینوکس و مک قابل استفاده هستند. درحال حاضر <a href="https://android-studio.ir/install-genymotion/">Genymotion</a>، <a href="https://www.bluestacks.com/">BlueStacks</a> و <a href="https://www.memuplay.com/">MEMu</a> جزء گزینه‌های مطرح به شمار می‌روند.</p>
<h2 id="avd-vs-other-android-emulators">چرا AVD؟</h2>
<p>در اندروید استودیو از همان ابتدای کار یک امولاتور داخلی در اختیار توسعه‌دهندگان قرار گرفته بود که امکان مدیریت دیوایس‌ها با استفاده از ابزار AVD فراهم شده است.<br />
بااینحال از سال ۱۳۹۵ تا اوایل ۱۴۰۱ با توجه به مزایایی که Genymotion نسبت به AVD داشت (مانند سرعت بالاتر و حجم کم دیوایس‌ها) شخصاً ترجیح می‌دادم از جنی موشن استفاده کنم.<br />
اما در ماه‌های اخیر سیاست جنی موشن در خصوص ارائه‌ی نسخه‌ی دسکتاپ این شبیه ساز تغییراتی داشته که مهم‌ترین آنها عدم انتشار API برای نسخه‌های جدید اندروید و اعمال محدودیت ۱ ماهه برای نسخه‌ی رایگان (Personal) هستند. از این به بعد تمرکز تیم Genymotion بر ارائه‌ی خدمات بر بستر Cloud است که هزینه‌های آن برای برنامه نویسان ایرانی بسیار بالاست.<br />
در این مدت مشکلات AVD مخصوصا مساله‌ی سرعت اجرا هم تا حدود زیادی رفع شد و با توجه به شرایط فعلی، استفاده از شبیه ساز داخلی اندروید استودیو منطقی‌ترین گزینه محسوب می‌شود. همین مساله باعث شد آموزش کار با امولاتور داخلی اندروید استودیو و AVD را تهیه کنم.</p>
<h2 id="install-android-studio-emulator-avd">نصب امولاتور اندروید استودیو</h2>
<p>امولاتور اندروید استودیو به صورت پیش‌فرض فعال نیست و باید از طریق SDK Manager نصب شود. همانطور که در آموزش نصب و راه اندازی اندروید استودیو گفته شد، امکان نصب آفلاین گزینه‌های Emulator و HAXM وجود ندارد و صرفا باید به صورت آنلاین نصب انجام شود.<br />
همچنین هنگام نصب اندروید استودیو باید گزینه‌ی Android Virtual Device انتخاب شده باشد در غیر اینصورت امکان استفاده از دیوایس مجازی یا AVD وجود نخواهد داشت:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/install/3/choose_android_virtual_device.png" alt="انتخاب گزینه AVD هنگام نصب اندروید استودیو" /><figcaption>انتخاب گزینه AVD هنگام نصب اندروید استودیو</figcaption></figure>
<p>پس از اطمینان از برقراری ارتباط اینترنت و تغییر IP برای عبور از تحریم ایران، SDK Manager را باز می‌کنم. اگر برای تغییر IP در اندروید استودیو نیاز به راهنمایی دارید این آموزش را مطالعه کنید.<br />
گزینه Android Emulator را انتخاب و تایید می‌کنم تا دانلود و نصب آن شروع شود.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_android_emulator.png" alt="نصب Emulator از طریق SDK Manager اندروید استودیو" /><figcaption>نصب امولاتور از طریق SDK Manager اندروید استودیو</figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_android_emulator_2.png" alt="نصب شبیه ساز اندروید استودیو" /></p>
<p>یک ابزار جانبی با نام Intel x86 Emulator Accelerator (Haxm) در لیست SDK وجود دارد که استفاده از آن بر روی پردازنده‌های اینتل باعث افزایش سرعت شبیه ساز می‌شود. بنابراین در صورتی که پردازنده شما Intel باشد حتما این ابزار را هم نصب کنید.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM.png" alt="نصب HAXM در اندروید استودیو" /><figcaption>نصب HAXM (Intel x86 Emulator Accelerator)</figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM_2.png" alt="نصب HAXM در اندروید استودیو" /></p>
<p>در این مرحله حداکثر میزانی از RAM که به امولاتور باید اختصاص داده شود را تعیین می‌کنیم که من همان عدد پیش‌فرض یعنی ۲ گیگابایت را انتخاب می‌کنم. البته این عدد بعداً هم قابل تغییر است.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM_3.png" alt="نصب Intel x86 Emulator Accelerator در اندروید استودیو" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/install_intel_HAXM_4.png" alt="نصب HAXM در اندروید استودیو" /></p>
<p>نصب HAXM هم به پایان رسید و حالا می‌توانیم از شبیه ساز و AVD استفاده کنیم.<br />
اگر به یاد داشته باشید قبلا در جلسه‌ی آموزش نصب اندروید استودیو توضیح دادیم که در SDK Platforms برای هر API چند System image مختلف وجود دارد که این سیستم ایمیج‌ها برای شبیه ساز داخلی اندروید استودیو استفاده می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_system_images_in_adk_manager.png" alt="System Image ها در SDK" /><figcaption>System Image ها در SDK</figcaption></figure>
<p>همانطور که در تصویر فوق مشاهده می‌کنید برای Android 12 به تعداد ۹ عدد System Image وجود دارد. توضیحات مربوط به هر سیستم ایمیج قبلا در قسمت توضیحات SDK در آموزش نصب اندروید استودیو قید شده بنابراین نیازی به تکرار نیست.<br />
امکان دانلود و نصب هرکدام از سیستم ایمیج‌ها از طریق SDK Manager وجود دارد اما برای جلوگیری از انتخاب گزینه اشتباه توصیه می‌کنم دانلود را در محیط AVD انجام دهید. در AVD سیستم ایمیج مناسب شما در تب Recommended قرار می‌گیرد و از دانلود سیستم ایمیج‌های غیر ضروری و حجم بالا جلوگیری می‌کند.<br />
گزینه Device Manager را از منوی تولبار یا نوار سمت راست اندروید استودیو انتخاب می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/android_studio_device_manager.png" alt="Device Manager اندروید استودیو" /><figcaption>Device Manager اندروید استودیو</figcaption></figure>
<p>پنجره Device Manager باز شد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/device_manager_virtual_tab.png" alt="قسمت دیوایس‌های مجازی در Device Manager" /><figcaption>قسمت دیوایس‌های مجازی در Device Manager</figcaption></figure>
<p>در دیوایس منیجر دو تب Virtual (مجازی) و Physical (فیزیکی) وجود دارد. تب Virtual برای مدیریت دیوایس‌های مجازی استفاده می‌شود. در حال حاضر هیچ دیوایس مجازی در لیست وجود ندارد.<br />
روی دکمه Create device یا لینک Create Virtual device کلیک می‌کنم تا پنل ساخت دیوایس مجازی باز شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_device_to_avd.png" alt="اضافه کردن دیوایس مجازی در AVD" /><figcaption>اضافه کردن دیوایس مجازی در AVD</figcaption></figure>
<p>در سمت چپ، دیوایس‌ها دسته‌بندی شده که در حالت پیش‌فرض گزینه Phone و یکی از دیوایس‌های آن انتخاب شده است. دیوایس‌های مختلفی در لیست وجود دارد که بر اساس متغیرهایی مانند سایز صفحه نمایش و رزولوشن آن مشخص شده‌اند.<br />
من فعلا سایز خاصی مدنظرم نیست بنابراین همین دیوایس پیش فرض را انتخاب می‌کنم برای مرحله بعد.<br />
دیوایسی که انتخاب شده دارای برنامه Play Store است بنابراین سیستم ایمیجی دانلود خواهد شد که از پلی استور پشتیبانی می‌کند.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/select_system_image.png" alt="انتخاب و دانلود System Image در AVD" /><figcaption>انتخاب و دانلود System Image</figcaption></figure>
<p>در این مرحله باید نسخه‌ی system image مدنظر خود را انتخاب کنیم. برای مثال من قصد دارم دیوایس را با API 31 یعنی Android 12 بسازم. بنابراین همین API را باید انتخاب کنم اما در حال حاضر این API و تمامی API های دیگر یک گزینه دانلود مقابلشان قرار داده شده که نشان می‌دهد سیستم ایمیج هیچکدام دانلود و نصب نشده.<br />
روی گزینه Download اندروید ۱۲ کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/download_system_image.png" alt="دانلود System Image" /><figcaption>شروع دانلود System Image</figcaption></figure>
<p>دانلود system image آغاز شد. خبر خوب اینکه مانند سایر ابزار SDK و برخلاف Emulator و HAXM فایل‌های مربوط به system image را می‌توان در خارج از محیط اندروید استودیو دانلود و به صورت آفلاین به SDK اضافه کرد.<br />
با توجه به حجم بالای سیستم ایمیج‌ها (حدود ۱٫۵ گیگابایت) و لزوم استفاده از ابزار تغییر IP احتمال شکست دانلود بالاست بنابراین توصیه می‌کنم لینک فایل سیستم ایمیج را از قسمت بالا کپی کرده و با استفاده از یک برنامه مدیریت دانلود، دریافت کنید.<br />
هرچند بازهم برای دانلود فایل نیاز به استفاده از ابزار تغییر IP وجود دارد اما در صورت استفاده از برنامه‌های مدیریت دانلود، چنانچه فرایند دانلود در هر نقطه قطع شود، می‌توان مجدد دانلود را از همان نقطه ادامه داد در حالی که در اندروید استودیو دانلود از ابتدا آغاز خواهد شد.<br />
اگر همچنان قصد دارید دانلود به صورت خودکار و در محیط اندروید استودیو انجام شود فقط کافیست صبر کنید تا دانلود فایل به اتمام برسد.<br />
اما من ترجیح می‌دهم فایل را جداگانه دانلود و سپس به محل نصب SDK منتقل کنم. بنابراین لینک فایل را کپی و فرایند دانلود را متوقف می‌کنم.<br />
پوشه SDK را باز می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_system-images_folder_to_sdk.png" alt="اضافه کردن System Image به SDK به صورت آفلاین و دستی" /><figcaption>اضافه کردن System Image به SDK به صورت آفلاین و دستی</figcaption></figure>
<p>مشاهده می‌کنید یک پوشه با نام system-images در SDK من وجود دارد. علت اینکه این پوشه قبلا ساخته شده این است که من در قسمت قبل یک سیستم ایمیج را برای دانلود انتخاب کردم و فرایند دانلود آغاز شد.<br />
به محض شروع دانلود، پوشه‌های لازم برای قرارگیری سیستم ایمیج‌ها ایجاد می‌شود حتی اگر دانلود متوقف شود.<br />
بنابراین اگر فایل سیستم ایمیج را قبلا دانلود کرده‌اید یا لینک آن را در اختیار دارید و نیازی به انجام مراحل قبل برای دریافت لینک دانلود ندارید، این پوشه هم خودکار ساخته نشده و خودتان باید آنرا با همین نام ایجاد کنید.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> لینک دانلود سیستم ایمیج‌های پرکاربرد در انتهای همین صفحه قرار می‌گیرد.
</div>
<p>وارد پوشه system-images می‌شوم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_system_image_folder_to_sdk.png" alt="اضافه کردن سیستم ایمیج در SDK اندروید استودیو" /></p>
<p>یک پوشه با نام android-31 وجود دارد که مربوط به Android 12 است که در AVD برای دانلود انتخاب کرده بودم. داخل این پوشه به ترتیب به این شکل ساخته شده:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_google_apis_playstore_folder.png" alt="پوشه google_apis_playstore" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/add_x86_64_folder.png" alt="پوشه x86_64" /></p>
<p>حالا کافیست پوشه x86_64 را حذف کرده و پوشه‌ای با همین نام که در فایل زیپ سیستم ایمیج قرار دارد را جایگزین کنم. نام فایلی که من دانلود کردم x86_64-31_r09.zip است. در نامگذاری فایل سیستم ایمیج از جزئیات آن استفاده شده است.<br />
X86_64 یعنی دیوایسی که با این سیستم ایمیج ساخته شود از اپلیکیشن‌های ۶۴ بیتی هم پشتیبانی می‌کند. عدد ۳۱ مربوط به نسخه API است و r09 یعنی فایل سیستم فعلی نهمین نسخه بروزرسانی شده از این سیستم ایمیج است.<br />
به این ترتیب می‌توان یک سیستم ایمیج را بدون نیاز به دانلود آن در محیط اندروید استودیو نصب کرد.<br />
در اینجا چون دانلود سیستم ایمیج اندروید ۱۲ برای لحظه‌ای آغاز شده بود، با اینکه آنرا متوقف کردم، تمامی پوشه‌های مربوط به آن به طور خودکار ساخته شد. اما اگر به هر دلیلی این فرایند انجام نشود، این پوشه‌ها باید به صورت دستی ایجاد شود.<br />
برای مثال این سیستم ایمیج مربوط به API 31 بود بنابراین نام پوشه اصلی آن android-31 است. دیوایس Pixel 2 که در AVD انتخاب کردیم از نوعی بود که از Play Store پشتیبانی می‌کرد بنابراین پوشه بعدی با نام google_apis_playstore ساخته شده. اگر دیوایس از نوعی باشد که پلی استور را پشتیبانی نمی‌کند پوشه را با نام google_apis می‌سازیم.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> برای هر دیوایس مثل Pixel 2 یک سیستم ایمیج جداگانه وجود ندارد. برای مثال برای API 31 فقط یک سیستم ایمیج وجود دارد که از پلی استور و از اپلیکیشن‌های x86_64 پشتیبانی می‌کند.<br />
به عبارت دیگر چنانچه در آینده چندین دیوایس دیگر با همین مشخصات بخواهیم اضافه کنیم باز هم از همین سیستم ایمیج استفاده می‌شود و نیاز به دانلود فایل دیگری نیست.
</div>
<p>پوشه x86_64 که از فایل زیپ دانلود شده خروجی گرفتم را جایگزین این پوشه کرده و به پنجره ساخت دیوایس برمی‌گردم. با توجه به اینکه سیستم ایمیج به صورت آفلاین نصب شد یکبار با استفاده از دکمه Refresh لیست سیستم ایمیج‌ها را بروز می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/system_image_added.png" alt="سیستم ایمیج اندروید استودیو" /><figcaption>سیستم ایمیج به AVD اضافه شد</figcaption></figure>
<p>مشاهده می‌کنید API 31 نصب و گزینه Download حذف شده است. همچنین با انتخاب این API دکمه Next فعال شده و می‌توان به مرحله بعد رفت.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_configuration.png" alt="تنظیمات دیوایس مجازی در AVD" /><figcaption>تنظیمات دیوایس مجازی</figcaption></figure>
<p>در این مرحله جزئیات دیوایس مجازی را می‌توان تغییر داد. مانند نام دیوایس و وضعیت افقی یا عمودی بودن دیوایس هنگام اجرا. با انتخاب گزینه Show Advanced Settings سایر تنظیمات فعال می‌شود.<br />
با کلیک روی Finish دیوایس ساخته شده و به لیست دیوایس‌های مجازی من اضافه می‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_created.png" alt="ایجاد دیوایس مجازی در AVD" /></p>
<p>در قسمت Actions چهار گزینه برای هر دیوایس وجود دارد. گزینه اول برای استارت دیوایس است. از زمان استارت دیوایس تا بوت شدن اندروید حدود ۲۰ ثانیه زمان لازم است که البته به سخت‌افزار شما بستگی دارد.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/start_avd.png" alt="استارت دیوایس مجازی در AVD اندروید استودیو" /><figcaption>استارت دیوایس مجازی در AVD</figcaption></figure>
<p>حالا می‌توان پروژه را روی این دیوایس Run یا Debug کرد. در نوار بالای دیوایس چند گزینه وجود دارد که کاربرد هرکدام مشخص است. یک گزینه‌ی ۳ نقطه هم وجود دارد که تنظیمات دستگاه مانند موقعیت مکانی، میکروفون، دوربین و سنسورها را در اختیار برنامه نویس قرار می‌دهد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/avd/avd_settings.png" alt="پنل تنظیمات دیوایس مجازی AVD" /><figcaption>پنل تنظیمات دیوایس مجازی AVD</figcaption></figure>
<p>این آموزش هم به پایان رسید. موفق و پیروز باشید.</p>
<h2 id="download-avd-system-images">دانلود System Image ها</h2>
<p>توجه داشته باشید دانلود این فایل‌ها تنها در صورتی لازم است که قصد نصب آفلاین سیستم ایمیج‌ها را داشته باشید.</p>
<p><span class="notice">تاریخ بروزرسانی فایل‌ها : ۱۴۰۱/۰۴/۲۵</span></p>
<p>» <a href="http://dl.android-studio.ir/sdk/system_images/x86_64-31_r09.zip">دانلود System Image Android 12 Google Play x86_64 (API 31) با حجم ۱٫۳ گیگابایت</a> | <a style="color:#A3C21B" href="http://dl2.android-studio.ir/sdk/system_images/x86_64-31_r09.zip">لینک کمکی</a></p>
<p>» <a href="http://dl.android-studio.ir/sdk/system_images/x86-24_r19.zip">دانلود System Image Android 7 Google Play x86 (API 24) با حجم ۷۷۵ مگابایت</a> | <a style="color:#A3C21B" href="http://dl2.android-studio.ir/sdk/system_images/x86-24_r19.zip">لینک کمکی</a></p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۲<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/04_1_emulator_avd.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/04_1_emulator_avd.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/android-emulator-avd/">کار با امولاتور و ساخت دیوایس مجازی (AVD)</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/android-emulator-avd/feed/</wfw:commentRss>
			<slash:comments>74</slash:comments>
		
		
			</item>
		<item>
		<title>خداحافظ findViewById؛ سلام View Binding</title>
		<link>https://android-studio.ir/android-view-binding/</link>
					<comments>https://android-studio.ir/android-view-binding/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Wed, 14 Jul 2021 19:35:20 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=197632</guid>

					<description><![CDATA[<p>فکر می‌کنم عنوان این مبحث تا حد زیادی هدف این جلسه را برای شما روشن کرده باشد. View Binding در اندروید جایگزینی شایسته برای findViewById است که کار معرفی یک view در اکتیویتی یا فرگمنت را برای ما انجام می‌داد. قبلا برای تعریف هر view لازم بود یکبار متد findViewById فراخوانی شود که علاوه بر [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/android-view-binding/">خداحافظ findViewById؛ سلام View Binding</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/android_view_binding.png" alt="آموزش View Binding در اندروید" /><br />
فکر می‌کنم عنوان این مبحث تا حد زیادی هدف این جلسه را برای شما روشن کرده باشد. View Binding در اندروید جایگزینی شایسته برای findViewById است که کار معرفی یک view در اکتیویتی یا فرگمنت را برای ما انجام می‌داد.<br />
قبلا برای تعریف هر view لازم بود یکبار متد findViewById فراخوانی شود که علاوه بر افزایش حجم کدها، وقت زیادی را از برنامه نویس اندروید می‌گرفت. در این جلسه به بررسی قابلیت View Binding می‌پردازیم که ما را از شر findViewById راحت کرده و باعث افزایش سرعت کار و همچنین کاهش حجم کدهای پروژه می‌گردد. البته مزایای دیگری هم دارد که در ادامه جلسه به آن می‌پردازیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#what-is-view-binding">View Binding چیست؟</a></li>
<li><a href="#bind-ways">مقایسه View Binding در اندروید با سایر ابزارها و روش‌ها</a>
<ul>
<li><a href="#findViewById">findViewById</a></li>
<li><a href="#butterknife">کتابخانه ButterKnife</a></li>
<li><a href="#data-binding">Data Binding</a></li>
<li><a href="#view-binding">View Binding</a></li>
</ul>
</li>
<li><a href="#android-view-binding-project">ساخت پروژه View Binding و بررسی آن</a>
<ul>
<li><a href="#enable-view-binding-in-android-studio">فعالسازی View Binding در اندروید استودیو</a></li>
<li><a href="#view-binding-in-activity">پیاده سازی View Binding در اکتیویتی</a></li>
<li><a href="#view-binding-classes">دسترسی به کلاس‌های View Binding</a></li>
<li><a href="#view-binding-include-layouts">View Binding و layout های include شده</a></li>
<li><a href="#ignore-view-binding">غیر فعال کردن View Binding برای یک لایه (layout) خاص</a></li>
<li><a href="#view-binding-in-fragment">پیاده سازی View Binding روی فرگمنت</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="what-is-view-binding">View Binding چیست؟</h2>
<p>به نام خدا. View Binding یکی از امکانات زیر مجموعه‌ی <a href="https://developer.android.com/jetpack" target="_blank" rel="noopener">Jetpack</a> است که در کنفرانس IO گوگل در سال ۲۰۱۹ معرفی و در سال ۲۰۲۰ در Android Studio 3.6 و به عبارت دیگر Gradle 3.6 امکان فعالسازی و استفاده از آن مهیا شد. بنابراین استفاده از این قابلیت تنها از این نسخه و به بالا امکان پذیر است.<br />
اولین مزیت View Binding این است که برای فعالسازی روی اندروید استودیو نیازی به افزودن (import) یک کتابخانه اضافی به پروژه اندرویدی نیست و درون پلاگین Gradle اندروید استودیو تعبیه شده است. بنابراین صرفا لازم است در فایل گریدلِ پروژه آنرا فعال کنیم.<br />
همانطور که در ابتدای جلسه اشاره شد، View Binding جایگزینی برای تعریف view ها به شیوه سنتی آن یعنی استفاده از findViewById به شمار می‌رود.<br />
اگر در حالت عادی در یک لایه XML تعداد ۱۰ عدد view (مانند Button، TextView، EditText و&#8230;) داشته باشیم برای هرکدام و به صورت جداگانه باید با استفاده از findViewById آنها را در Activity یا Fragment تعریف کنیم. چیزی شبیه به اکتیویتی زیر:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.testapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button sendBtn;
    private TextView textView;
    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.send_btn);
        textView = findViewById(R.id.txt_view);
        editText = findViewById(R.id.edit_text);

    }
}
</pre>
<p>علاوه بر نیاز به نوشتن و تکرار کدهای اضافی، ایرادات و باگ‌هایی این شیوه‌ی معرفی view ها دارد که با جایگزینی آن با View Binding این ایرادات مرتفع می‌گردد.<br />
با استفاده از View Binding نیازی به تعریف view ها توسط برنامه نویس نبوده و فراخوانی view ها به صورت مستقیم توسط id (شناسه) آنها انجام می‌شود!</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> اگر قبلا اصطلاح Data Binding را در جایی دیده یا شنیده‌اید آنرا با View Binding اشتباه نگیرید. البته تا قبل از معرفی View Binding از Data Binding هم برای انجام این کار استفاده می‌شد اما از آنجایی که قابلیت‌های Data Binding به حذف findViewById محدود نمی‌شود، تیم توسعه اندروید زیرمجموعه‌ای از آن را با نام View Binding معرفی کرد که به صورت تخصصی تنها یک وظیفه را برعهده داشته باشد.<br />
ضمن اینکه در مستندات اندروید هم توصیه شده چنانچه صرفا به قابلیت تعریف view ها در پروژه خود نیاز داشته باشیم بجای Data Binding از View Binding استفاده کنیم. بکارگیری View Binding نسبت به Data Binding ساده‌تر بوده و کارایی (Performance) بهتری نیز به همراه خواهد داشت.
</div>
<h2 id="bind-ways">مقایسه View Binding در اندروید با سایر ابزارها و روش‌ها</h2>
<p>برای تعریف view ها در اندروید چندین روش و کتابخانه وجود دارد که هرکدام مزایا و معایب مخصوص به خود را داشته و البته در نهایت به این نتیجه می‌رسیم که استفاده از View Binding در اندروید بهینه ترین روش فعلی خواهد بود.<br />
در ادامه برای درک بهتر برتری View Binding نسبت به رقبا، به بررسی هرکدام می‌پردازیم:</p>
<h3 id="findViewById">findViewById</h3>
<p>نیازی به معرفی نیست بنابراین به بررسی مزایا و معایب می‌پردازم:<br />
<b>عدم تاثیر در سرعت بیلد:</b> شاید تنها مزیت آن را بتوان عدم تاثیر بر سرعت بیلد پروژه دانست چرا که هنگام اجرای برنامه کار می‌کند. اما این مزیت در برابر معایب متعدد آن اصلا قابل توجیه نیست.<br />
<b>کدهای اضافی:</b> اولین امتیاز منفی این است که به ازاء هر view می‌بایست یک متغیر جداگانه ایجاد شود که طبیعتا حجم کدهای ما را افزایش خواهد داد.<br />
<b>Type safety نیست:</b> واژه type به معنی &#8220;نوع&#8221; و safety به معنی &#8220;ایمنی&#8221; است. Type safety نبودن findViewById به این معنی است که در هنگام تعریف کردن یک view امکان بروز اشتباه در تعیین نوع آن وجود دارد.<br />
برای مثال ممکن است view از جنس TextView باشد و ما اشتباها آن را در یک متغیر از جنس EditText تعریف کنیم.<br />
<b>Null safe نیست:</b> اگر یک view موجود در layout فقط در شرایطی خاص در دسترس باشد ممکن است با یک خطای NullPointerException مواجه شویم. زیرا findViewById وضعیت نال بودن یا نبودن view را در شرایط مختلف بررسی نمی‌کند.<br />
برای مثال حالتی را درنظر بگیرید که دو نسخه از activity_main.xml در پروژه داریم که یکی برای حالت عادی (صفحه عمودی یا portrait) و دیگری برای حالت افقی (landscape) استفاده می‌شود. چنانچه یک view در هردو layout مشترک نبوده و تنها در حالت portrait بکار رفته باشد هنگام قرار گرفتن دستگاه کاربر در حالت landscape می‌تواند سبب بروز خطای null شود.</p>
<h3 id="butterknife">کتابخانه ButterKnife</h3>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/butterknife_android_library.png" alt="کتابخانه ButterKnife" /></p>
<p>تا قبل از معرفی Data Binding توسط تیم اندروید، <a href="https://jakewharton.github.io/butterknife/" target="_blank" rel="noopener">کتابخانه ButterKnife</a> یکی از پرکاربردترین ابزار برای این منظور به شمار می‌رفت. البته کاربرد این کتابخانه محدود به bind (متصل) کردن view ها نمی‌شود و برای resource ها مانند رشته‌ها، رنگ‌ها و سایزها نیز کاربرد دارد. اما در این جلسه تنها قسمت مربوط به view ها مدنظر ماست.<br />
<b>کاهش حجم کدها:</b> استفاده از این کتابخانه حجم کدها را نسبت به findViewById کاهش داده و به عبارتی کدهای تمیز تری می‌نویسیم. در این روش، به وسیله Annotation ها (حاشیه نویسی) می‌توان view های مدنظر را تعریف کرد. سه view ای که در قسمت قبل توسط findViewById تعریف شده بود را اینبار با استفاده از ButterKnife تعریف می‌کنم.<br />
پس از اضافه کردن کتابخانه به پروژه، توسط annotation با نام <span dir="ltr">@BindView</span> هرکدام از view ها قابل تعریف است:</p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.testapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import butterknife.BindView;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.send_btn) Button sendBtn;
    @BindView(R.id.txt_view) TextView textView;
    @BindView(R.id.edit_text) EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
}
</pre>
<p><b>Type safety نیست:</b> مانند findViewById کتابخانه ButterKnife هم type safety نیست و id هر نوع view ای را می‌توان به هر نوع View در کلاس جاوا متصل کرد. بنابراین ممکن است در هنگام اجرا با ارور Exception برخورد کنیم.<br />
<b>کاهش سرعت بیلد:</b> از آنجایی که در حین کامپایل پروژه یک پردازشگر Annotation برای تولید (generate) کدها اجرا می‌شود در سرعت بیلد تاثیر منفی می‌گذارد. هرچند این کاهش سرعت بیلد چیزی نیست که ما را از استفاده یک کتابخانه منصرف کند زیرا به قول معروف مزایای آن بر معایبش می‌چربد. البته نه الان که View Binding را داریم!</p>
<h3 id="data-binding">Data Binding</h3>
<p>تفاوت اساسی Data Binding با دو مورد قبل در این است که پس از فعالسازی این قابلیت در پروژه اندرویدی، به ازاء هر layout یا سایر resource ها به صورت خودکار یک کلاس ایجاد (generate) می‌شود. در این کلاس‌ها تمامی عناصر به صورت خودکار تعریف و مقدار دهی شده و به سادگی می‌توانیم در <a href="http://android-studio.ir/activity/" target="_blank" rel="noopener">اکتیویتی</a> یا <a href="http://android-studio.ir/android-fragments/" target="_blank" rel="noopener">فرگمنت</a> به آنها دسترسی داشته باشیم.<br />
<b>کاهش حجم کدها:</b> فعالسازی و استفاده از Data Binding نسبت به دو گزینه قبل نیاز به نوشتن کد کمتری دارد در نتیجه کدهای تمیزتری خواهیم داشت.<br />
<b>کاهش سرعت بیلد:</b> در کلاس‌های ساخته شده توسط Data Binding از Annotation ها برای تعریف منابع استفاده می‌شود بنابراین مانند ButterKnife این مساله در کاهش سرعت build شدن پروژه تاثیرگذار خواهد بود.<br />
<b>Type safety و Null safe است:</b> هردو مورد در Data Binding صدق می‌کند و از این بابت جای نگرانی نداریم.<br />
در خصوص Data Binding بیشتر از این به بیان جزئیات نمی‌پردازم. </p>
<h3 id="view-binding">View Binding</h3>
<p>رسیدیم به هدف! همانطور که در ابتدای مبحث گفته شد View Binding به نوعی زیر مجموعه‌ی Data Binding محسوب می‌شود که کاربرد آن در حذف findViewById خلاصه شده و به همین جهت علاوه بر ساده تر شدن فرآیند، نسبت به Data Binding خروجی بهینه‌تری را در اختیار ما قرار می‌دهد.<br />
<b>کاهش حجم کدها:</b> با فعالسازی و استفاده از View Binding حجم کد مورد نیاز برای استفاده از view ها به حداقل ممکن می‌رسد و در بین این ۴ گزینه بالاترین امتیاز را به خود اختصاص داده است. تنها با نوشتن id هر view در اکتیویتی یا فرگمنت به view مربوطه دسترسی خواهیم داشت!<br />
<b>افزایش سرعت بیلد:</b> شیوه کار View Binding هم مانند Data Binding است و با فعالسازی آن به ازاء هر layout یک کلاس ایجاد شده و تمامی view های آن درون کلاس به صورت خودکار تعریف می‌شود. اما در اینجا خبری از پردازش annotation ها نیست و در نتیجه سرعت بیلد به نسبت Data Binding بالاتر خواهد بود.<br />
<b>Type safety و Null safe است:</b> این دو مورد را از پدر خودش یعنی Data Binding به ارث برده و با یکدیگر مشترک هستند. </p>
<p>حالا که گزینه‌های مختلف را بررسی کردیم و فهمیدیم بهترین گزینه در حال حاضر View Binding است در ادامه جلسه این قابلیت را در قالب یک پروژه بررسی و تمرین می‌کنیم.</p>
<h2 id="android-view-binding-project">ساخت پروژه View Binding و بررسی آن</h2>
<p>ابتدا طبق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام ViewBinding می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
در ابتدا و قبل از هرچیز لازم است قابلیت View Binding را در پروژه خود در اندروید استودیو فعال کنیم.</p>
<h3 id="enable-view-binding-in-android-studio">فعالسازی View Binding در اندروید استودیو</h3>
<p>همانطور که قبلا اشاره شد این قابلیت به صورت پیش فرض در گریدل نسخه ۳٫۶ به بالا تعبیه شده و برای فعالسازی آن نیازی به اضافه کردن کتابخانه به پروژه نیست.<br />
برای فعالسازی کافیست بلاک زیر را درون بلاک android در فایل build.gradle (project) اضافه و سپس پروژه را Sync کنم:</p>
<pre class="brush: java; title: ; notranslate">
buildFeatures {
    viewBinding = true
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> چنانچه از اندروید استودیو ۳٫۶ و تا قبل از ۴٫۰ استفاده می‌کنید فعالسازی View Binding به صورت زیر انجام می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
viewBinding {
    enabled = true
}
</pre>
<p>البته واضح است که استفاده از نسخه‌های قدیمی اندروید استودیو توصیه نمی‌شود.
</p></div>
<h3 id="view-binding-in-activity">پیاده سازی View Binding در اکتیویتی</h3>
<p>همانطو که قبلا گفته شد، با فعالسازی View Binding به ازاء هر layout موجود در پروژه یک کلاس جداگانه ایجاد می‌شود. نام هر کلاس از نام layout مربوط به آن گرفته می‌شود که البته به صورت camel case نوشته شده و پسوند Binding هم به انتهای آن اضافه می‌شود.<br />
برای مثال در این پروژه ما یک layout با نام activity_main.xml داریم. کلاسی که برای این layout ایجاد می‌شود ActivityMainBinding.java نام دارد. به عنوان یک مثال دیگر چنانچه در آینده یک layout با نام activity_map.xml به پروژه اضافه کنیم، کلاس ایجاد شده ActivityMapBinding.java نام خواهد داشت.<br />
در این کلاس به ازاء هر view (ویجت) ای که در activity_main.xml وجود داشته و یک id به آن اختصاص داده شده باشد، یک متغیر ایجاد می‌شود و ما در اکتیویتی به آن دسترسی خواهیم داشت.<br />
در layout پیش فرض پروژه یک View Group از جنس <a href="http://android-studio.ir/constraintlayout/" target="_blank" rel="noopener">ConstraintLayout</a> و یک TextView وجود دارد که البته برای TextView شناسه (id) به صورت پیش فرض تعریف نشده. بنابراین برای آنکه به این view دسترسی داشته باشم یک id به آن اختصاص می‌دهم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;TextView
      android:id=&quot;@+id/txt_view&quot;
      android:layout_width=&quot;wrap_content&quot;
      android:layout_height=&quot;wrap_content&quot;
      android:text=&quot;Hello World!&quot;
      app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
      app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
      app:layout_constraintRight_toRightOf=&quot;parent&quot;
      app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

  &lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>در مرحله بعد لازم است یک آبجکت (شیء) از کلاس ActivityMainBinding داخل اکتیویتی تعریف کنیم تا به view ها به صورت مستقیم دسترسی داشته باشیم.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/create_object_from_binding_class_in_activity.png" alt="ساخت آبجکت از کلاس View Binding" /><figcaption>ساخت آبجکت از کلاس View Binding</figcaption></figure>
<p>مشاهده می‌کنید کلاس ActivityMainBinding توسط اندروید استودیو شناسایی می‌شود. یعنی کلاس قبلا ساخته شده و قابل استفاده است.</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.viewbinding;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import ir.android_studio.viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mainBinding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(R.layout.activity_main);


    }
}
</pre>
<p>نام دلخواه mainBinding را برای کلاس انتخاب کردم. سپس درون متد onCreate و قبل از setContentView توسط متد inflate آنرا مقداردهی کردم.<br />
حالا به View Group اصلی layout و تمام view هایی که قبلا به layout اضافه شده و یا بعد از این اضافه شود دسترسی خواهیم داشت. البته مجدد تاکید می‌کنم view هایی که دارای ویژگی id باشند. بجز عنصر اصلی یا ریشه‌ی layout که نیازی به id نداشته و در متغیری با نام root ذخیره شده و توسط getRoot در دسترس است. در اینجا عنصر ریشه ما یک ConstraintLayout است که قبلا در جلسه <a href="http://android-studio.ir/constraintlayout/" target="_blank" rel="noopener">آموزش کار با ConstraintLayout</a> با آن آشنا شدیم.<br />
ابتدا لازم است getRoot را به متد setContentView پاس بدهیم تا به اکتیویتی اعلام شود از طریق آبجکت Binding ای که ساخته‌ایم به layout دسترسی داشته باشد. getRoot را جایگزین R.layout.activity_main می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
setContentView(mainBinding.getRoot());
</pre>
<p>حالا برای تست View Binding با استفاده از متد setText یک متن را روی txt_view چاپ می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mainBinding = ActivityMainBinding.inflate(getLayoutInflater());

    setContentView(mainBinding.getRoot());

    mainBinding.txtView.setText(&quot;تست ViewBinding&quot;);

}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> نحوه نامگذاری id ها توسط View Binding به صورت camel case است. بنابراین همانطور که در کد فوق مشاهده می‌کنید txtView مربوط به txt_view موجود در layout است.
</div>
<p>پروژه را روی <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">امولاتور (شبیه ساز) اندرویدی</a> اجرا می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/uset_View_Binding_in_android_activity.png" alt="پیاده سازی View Binding در اکتیویتی" /><figcaption>پیاده سازی View Binding در اکتیویتی</figcaption></figure>
<p>طبق تصویر فوق هم layout نمایش داده شد و هم متنی که در setText قرار داده بودیم.<br />
برای تمرین و آشنایی بیشتر، یک Button به layout اضافه کرده و در اکتیویتی یک setOnClickListener برای آن تعریف می‌کنم. قبلا در <a href="http://android-studio.ir/event-handling/" target="_blank" rel="noopener">آموزش کار با رویدادها در اندروید</a> با این متد آشنا شده‌ایم. خط مربوط به setText را به درون رویداد مربوط به دکمه منتقل می‌کنم تا بعد از کلیک روی دکمه اجرا شود:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/set_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;80dp&quot;
        android:text=&quot;جایگذاری متن&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/txt_view&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/set_btn&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.viewbinding;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

import ir.android_studio.viewbinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mainBinding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(mainBinding.getRoot());

        mainBinding.setBtn.setOnClickListener(view -&gt; {

            mainBinding.txtView.setText(&quot;تست ViewBinding&quot;);

        });

    }
}
</pre>
<p>مجدد پروژه را اجرا کرده و روی دکمه جایگذاری متن کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/onClick_event_using_View_Binding.png" alt="دسترسی به Button توسط View Binding" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/onClick_event_using_View_Binding_2.png" alt="دسترسی به TextView توسط View Binding" /><figcaption></figcaption></figure>
<h3 id="view-binding-classes">دسترسی به کلاس‌های View Binding</h3>
<p>اگر مایل بودید به محتوای کلاس‌های ساخته شده توسط View Binding دسترسی داشته باشید، بعد از بیلد شدن پروژه (یعنی هنگام اجرای پروژه روی دیوایس یا گزینه Rebuild Project در تب Build و یا گرفتن خروجی APK از پروژه) در مسیر زیر فایل کلاس‌ها در دسترس هستند:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/View_Binding_classes_direcroty.png" alt="مسیر کلاس‌های View Binding" /><figcaption>مسیر کلاس‌های View Binding</figcaption></figure>
<p>برای نمایش محتوای دایرکتوری build لازم است نحوه نمایش پروژه در حالت Project و یا Project Files قرار گیرد. البته در پوشه‌ای که پروژه ذخیره شده هم می‌توان به فایل‌ها دسترسی داشت.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در صورت عدم نمایش کلاس‌های مربوط به View Binding در قسمت نمایش ساختار پروژه در اندروید استودیو و البته بعد از بیلد شدن پروژه، راست کلیک کرده و Reload from Disk کنید تا محتوای پروژه بروز شود.
</div>
<p>محتوای فعلی کلاس ActivityMainBinding.java پروژه من به اینصورت است:</p>
<p><span class="filename">ActivityMainBinding.java</span></p>
<pre class="brush: java; title: ; notranslate">
// Generated by view binder compiler. Do not edit!
package ir.android_studio.viewbinding.databinding;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.viewbinding.ViewBinding;
import ir.android_studio.viewbinding.R;
import java.lang.NullPointerException;
import java.lang.Override;
import java.lang.String;

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;

  @NonNull
  public final Button setBtn;

  @NonNull
  public final TextView txtView;

  private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull Button setBtn,
      @NonNull TextView txtView) {
    this.rootView = rootView;
    this.setBtn = setBtn;
    this.txtView = txtView;
  }

  @Override
  @NonNull
  public ConstraintLayout getRoot() {
    return rootView;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    View root = inflater.inflate(R.layout.activity_main, parent, false);
    if (attachToParent) {
      parent.addView(root);
    }
    return bind(root);
  }

  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    int id;
    missingId: {
      id = R.id.set_btn;
      Button setBtn = rootView.findViewById(id);
      if (setBtn == null) {
        break missingId;
      }

      id = R.id.txt_view;
      TextView txtView = rootView.findViewById(id);
      if (txtView == null) {
        break missingId;
      }

      return new ActivityMainBinding((ConstraintLayout) rootView, setBtn, txtView);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException(&quot;Missing required view with ID: &quot;.concat(missingId));
  }
}
</pre>
<div class="alert alert-warning">
<span class="notice">تذکر:</span> همانطور که در کامنت ابتدای کلاس ذکر شده این کلاس توسط کامپایلر View Binding ساخته شده و نباید به هیچ عنوان به صورت دستی ویرایش شود.
</div>
<p>ملاحظه می‌کنید برای همه متغیرها انوتیشین <span dir="ltr">@NonNull</span> قید شده یعنی هیچکدام از آیتم‌ها تحت هیچ شرایطی نال نخواهند شد.<br />
اما فرض کنید در پروژه خود یک activity_main.xml دیگر هم داشته باشیم که مربوط به طراحی صفحه در حالت Landscape (افقی) باشد و در این حالت یکی از view ها را حذف کرده باشیم. یا بلعکس یک view در این حالت اضافه کرده باشیم که قرار نیست در layout اصلی یعنی حالت عمودی وجود داشته باشد. در اینصورت بجای <span dir="ltr">@NonNull</span> انوتیشین <span dir="ltr">@Nullable</span> جایگزین خواهد شد که نشان می‌دهد این view می‌تواند در شرایطی نال باشد. بنابراین وضعیت نال مدیریت شده و به اصطلاح Null safe هستند.<br />
به متد انتهای کلاس دقت کنید:</p>
<pre class="brush: java; title: ; notranslate">
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
  // The body of this method is generated in a way you would not otherwise write.
  // This is done to optimize the compiled bytecode for size and performance.
  int id;
  missingId: {
    id = R.id.set_btn;
    Button setBtn = rootView.findViewById(id);
    if (setBtn == null) {
      break missingId;
    }

    id = R.id.txt_view;
    TextView txtView = rootView.findViewById(id);
    if (txtView == null) {
      break missingId;
    }

    return new ActivityMainBinding((ConstraintLayout) rootView, setBtn, txtView);
  }
  String missingId = rootView.getResources().getResourceName(id);
  throw new NullPointerException(&quot;Missing required view with ID: &quot;.concat(missingId));
}
</pre>
<p>حدس می‌زنم انتظارش را نداشتید اینجا با findViewById مواجه شوید! اما واقعیت جز این نیست 🙂<br />
View Binding از متد findViewById برای اتصال view ها به اکتیویتی استفاده می‌کند و انجام این مرحله تکراری و زجر آور را از روی دوش برنامه نویس برمی‌دارد. علاوه بر آن، در خصوص type و null هم ما را ایمن نگه می‌دارد.</p>
<h3 id="view-binding-include-layouts">View Binding و layout های include شده</h3>
<p>اگر بخاطر داشته باشید در جلسه <a href="http://android-studio.ir/android-navigation-drawer" target="_blank" rel="noopener">آموزش کار با Navigation Drawer</a> با استفاده از تگ include یک layout را درون layout اصلی برنامه اضافه کردیم.<br />
دسترسی به view های layout ای که در layout یک اکتیویتی include شده امکان پذیر است. یک layout جدید با نام layout_bottom.xml به پروژه اضافه کرده و یک TextView با شناسه btm_txt داخل آن تعریف می‌کنم:</p>
<p><span class="filename">layout_bottom.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;TextView
        android:id=&quot;@+id/btm_txt&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;متن پایینی&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;
    
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>سپس layout را درون activity_main.xml توسط تگ include اضافه می‌کنم. دقت داشته باشید برای دسترسی به layout ضمیمه شده و view های درون آن توسط View Binding حتما برای تگ include هم باید یک id تعریف شود. من شناسه include_layout را انتخاب کردم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/set_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;80dp&quot;
        android:text=&quot;جایگذاری متن&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/txt_view&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;252dp&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/set_btn&quot; /&gt;

    &lt;include
        layout=&quot;@layout/layout_bottom&quot;
        android:id=&quot;@+id/include_layout&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/txt_view&quot;
        app:layout_constraintVertical_bias=&quot;0.706&quot;
        tools:layout_editor_absoluteX=&quot;-16dp&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>حالا برای دسترسی به btm_txt در layout_bottom به اینصورت عمل می‌کنیم:</p>
<pre class="brush: java; title: ; notranslate">
mainBinding.includeLayout.btmTxt
</pre>
<p>includeLayout همان شناسه include_layout است.<br />
برای مثال متد setText را برای این TextView استفاده می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
mainBinding.setBtn.setOnClickListener(view -&gt; {

    mainBinding.txtView.setText(&quot;تست ViewBinding&quot;);
    mainBinding.includeLayout.btmTxt.setText(&quot;تعویض متن پایینی&quot;);

});
</pre>
<p>برای layout_bottom.xml هم یک کلاس ایجاد شده که LayoutBottomBinding.java نام دارد اما به دلیل اینکه در اینجا layout را درون یک layout دیگر include کرده‌ایم که قبلا یک آبجکت از آن در اکتیویتی ساخته شده، بدون نیاز به ساخت آبجکت جدید از layout زیرمجموعه، می‌توان به آن دسترسی داشت.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/included_layout_View_Binding_class.png" alt="دسترسی به view یک لایه include شده در اکتیویتی" /><figcaption>دسترسی به view یک لایه include شده در اکتیویتی</figcaption></figure>
<p>با اجرای پروژه و کلیک روی دکمه جایگذاری متن، متن این TextView هم تغییر می‌کند:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/View_Binding_for_include_layouts.png" alt="دسترسی به view یک لایه include شده در اکتیویتی" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/View_Binding_for_include_layouts_2.png" alt="دسترسی به view یک لایه include شده در اکتیویتی" /></p>
<h3 id="ignore-view-binding">غیر فعال کردن View Binding برای یک لایه (layout) خاص</h3>
<p>ممکن است در پروژه خود برای یک یا چند layout نیازی به ساخت کلاس View Binding نداشته باشیم. با توجه به اینکه افزایش تعداد کلاس‌ها در نهایت موجب کاهش سرعت بیلد و همچنین افزایش حجم پروژه می‌شود لذا می‌طلبد برای لایه‌هایی که به صورت ایستا (static) هستند و کاری با view های داخل آن نداریم، قابلیت View Binding را غیر فعال کنیم.<br />
برای انجام این کار کافیست خط زیر به تگ layout ریشه آن اضافه شود:</p>
<pre class="brush: xml; title: ; notranslate">
tools:viewBindingIgnore=&quot;true&quot;
</pre>
<p>برای مثال من یک لایه با نام layout_top.xml در پروژه ایجاد کرده‌ام و با توجه به ایستا بودن محتوای آن قصد دارم View Binding را روی این فایل غیر فعال کنم:</p>
<p><span class="filename">layout_top.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    tools:viewBindingIgnore=&quot;true&quot;&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>واژه ignore به معنی &#8220;نادیده گرفتن&#8221; و &#8220;رد کردن&#8221; است بنابراین چنانچه برای این ویژگی مقدار true تعیین شود، قابلیت View Binding برای layout مدنظر نادیده گرفته خواهد شد.</p>
<h3 id="view-binding-in-fragment">پیاده سازی View Binding روی فرگمنت</h3>
<p>قبلا در مبحث <a href="http://android-studio.ir/android-fragments/" target="_blank" rel="noopener">فرگمنت‌ها در اندروید</a> با کاربرد Fragment آشنا شدیم. استفاده از View Binding در فرگمنت تفاوت زیادی با اکتیویتی ندارد.<br />
یک فرگمنت با نام TestFragment به پروژه اضافه می‌کنم. سپس در layout فرگمنت یعنی fragment_text.xml یک TextView با شناسه fragment_txt تعریف می‌کنم:</p>
<p><span class="filename">fragment_test.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;FrameLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.TestFragment&quot;&gt;

    &lt;!-- TODO: Update blank fragment layout --&gt;
    &lt;TextView
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:text=&quot;متن پیش فرض فرگمنت&quot;
        android:id=&quot;@+id/fragment_txt&quot;/&gt;

&lt;/FrameLayout&gt;
</pre>
<p>سپس در کلاس فرگمنت مانند آنچه قبلا در اکتیویتی انجام شد یک آبجکت از کلاسی که View Binding برای layout فرگنت ایجاد کرده می‌سازم:</p>
<p><span class="filename">TextFragment.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.viewbinding;

import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import ir.android_studio.viewbinding.databinding.FragmentTestBinding;

public class TestFragment extends Fragment {

    private FragmentTestBinding frgBinding;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        //return inflater.inflate(R.layout.fragment_test, container, false);

        frgBinding = FragmentTestBinding.inflate(inflater, container, false);

        return frgBinding.getRoot();

    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();

        frgBinding = null;

    }
}
</pre>
<p>مطابق کد فوق ابتدا از کلاس FragmentTestBinding یک نمونه با نام frgBinding ساخته‌ام. سپس inflate اصلی فرگمنت را حذف (کامنت) کرده و inflate مربوط به View Binding را مانند اکتیویتی و البته اندکی تفاوت بجای آن تعریف کرده‌ام. در خط بعد هم متد getRoot برگردانده یا return شد.<br />
همچنین متد onDestroyView که مربوط به چرخه حیات فرگمنت هست را اضافه و درون آن آبجکت ساخته شده از View Binding را null می‌کنم تا هنگام حذف فرگمنت از اکتیویتی، این آبجکت نال شود.<br />
یک متد setText برای TextView فرگمنت در onCreateView و قبل از دستور return تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    
    //return inflater.inflate(R.layout.fragment_test, container, false);

    frgBinding = FragmentTestBinding.inflate(inflater, container, false);

    frgBinding.fragmentTxt.setText(&quot;متن جدید فرگمنت&quot;);

    return frgBinding.getRoot();

}
</pre>
<p>در نهایت، فرگمنت را در activity_main.xml تعریف می‌کنم. از اختصاص id به تگ fragment فراموش نکنید!</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/set_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;80dp&quot;
        android:text=&quot;جایگذاری متن&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

    &lt;TextView
        android:id=&quot;@+id/txt_view&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:layout_marginTop=&quot;252dp&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/set_btn&quot; /&gt;

    &lt;include
        android:id=&quot;@+id/include_layout&quot;
        layout=&quot;@layout/layout_bottom&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/txt_view&quot;
        tools:layout_editor_absoluteX=&quot;-16dp&quot; /&gt;

    &lt;fragment
        android:id=&quot;@+id/fragment_one&quot;
        android:name=&quot;ir.android_studio.viewbinding.TestFragment&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/include_layout&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>با اجرای مجدد پروژه می‌بینیم که متن موجود در setText روی TextView جایگزین متن پیش فرض آن شده:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/viewbinding/use_View_Binding_in_fragment.png" alt="پیاده سازی View Binding در فرگمنت" /><figcaption>پیاده سازی View Binding در فرگمنت</figcaption></figure>
<p>این جلسه هم به پایان رسید.<br />
موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developer.android.com/topic/libraries/view-binding" target="_blank" rel="noopener">https://developer.android.com/topic/libraries/view-binding</a><br />
<a href="https://developer.android.com/topic/libraries/data-binding" target="_blank" rel="noopener">https://developer.android.com/topic/libraries/data-binding</a>
</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۸<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/05_4_ViewBinding.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/05_4_ViewBinding.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/android-view-binding/">خداحافظ findViewById؛ سلام View Binding</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/android-view-binding/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
		<item>
		<title>دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه</title>
		<link>https://android-studio.ir/receive-sms-in-android-programming/</link>
					<comments>https://android-studio.ir/receive-sms-in-android-programming/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Mon, 31 May 2021 13:34:18 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=197009</guid>

					<description><![CDATA[<p>کاربردهای متعددی را برای قابلیت دریافت پیامک (SMS) در برنامه نویسی اندروید می‌توان برشمرد. با گسترش کاربرد اپلیکیشن‌های موبایلی و بویژه اندرویدی، حجم زیادی از خدمات بخش خصوصی و سازمانی در این محیط به مردم ارائه می‌گردد. برای مثال احراز هویت کاربر/مشتری یکی از آیتم‌های ضروری در ارائه این خدمات به شمار می‌رود که ارسال [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/receive-sms-in-android-programming/">دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/receive_sms_in_android_programming.png" alt="دریافت پیامک (SMS) در برنامه نویسی اندروید" /><br />
کاربردهای متعددی را برای قابلیت دریافت پیامک (SMS) در برنامه نویسی اندروید می‌توان برشمرد. با گسترش کاربرد اپلیکیشن‌های موبایلی و بویژه اندرویدی، حجم زیادی از خدمات بخش خصوصی و سازمانی در این محیط به مردم ارائه می‌گردد.<br />
برای مثال احراز هویت کاربر/مشتری یکی از آیتم‌های ضروری در ارائه این خدمات به شمار می‌رود که ارسال پیامک کد تایید به شماره همراه شخص یکی از رایج‌ترین روش‌هاست. اما برای بهبود UX (تجربه کاربری) می‌توان این بخش را به صورت خودکار انجام داد تا کاربر ملزم به وارد کردن دستی کد دریافتی نبوده و کد موجود در پیامک به طور خوکار وارد برنامه شود.<br />
البته کاربردهای دیگری هم برای دسترسی به پیامک‌های دریافتی وجود دارد و محدود به احراز هویت نیست. در این جلسه نحوه‌ی دریافت پیامک (SMS) در برنامه نویسی اندروید را به دو روش متفاوت بررسی می‌کنیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#how-to-get-sms-in-android">نحوه‌ دریافت پیامک در اپلیکیشن اندرویدی</a></li>
<li><a href="#android-receive-sms-project">ساخت پروژه دریافت SMS در اندروید استودیو</a>
<ul>
<li><a href="#receive-sms-using-SmsMessage">دریافت پیامک (SMS) توسط SmsMessage</a></li>
<li><a href="#receive-sms-using-Cursor">دریافت پیامک (SMS) توسط Cursor</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="how-to-get-sms-in-android">نحوه‌ دریافت پیامک در اپلیکیشن اندرویدی</h2>
<p>به نام خدا. به طور کل با استفاده از دو کلاس SmsMessage و Cursor می‌توانیم به پیامک‌های موجود در دستگاه اندرویدی دسترسی داشته باشیم. البته روش متداول، استفاده از SmsMessage است. در ادامه به بررسی هر دو روش پرداخته و سپس به صورت عملی پیاده سازی می‌کنیم.<br />
<b>SmsMessage:</b> کلاسی با نام SmsMessage در API 4 به <a href="http://android-studio.ir/introduction-android-operating-system/" target="_blank" rel="noopener">سیستم عامل اندروید</a> اضافه شده که وظیفه دسترسی به پیامک یا همان SMS را در دستگاه‌های اندرویدی بر عهده دارد.<br />
البته از API 1 هم کلاسی با این نام وجود داشت (<a href="https://developer.android.com/reference/android/telephony/gsm/SmsMessage" target="_blank" rel="noopener">android.telephony.gsm.SmsMessage</a>) که تنها از پروتکل GSM پشتیبانی می‌کرد. اما در API 4 این کلاس با <a href="https://developer.android.com/reference/android/telephony/SmsMessage" target="_blank" rel="noopener">android.telephony.SmsMessage</a> جایگزین شد که از هر دو پروتکل GSM و  CDMA پشتیبانی می‌کند.<br />
همچنین برای شنود رویداد مربوط به دریافت پیامک لازم است از برودکست رسیور استفاده کنیم که قبلا در <a href="http://android-studio.ir/android-broadcastreceiver-component/" target="_blank" rel="noopener">آموزش BroadcastReceiver در اندروید</a> با این مبحث آشنا شدیم.<br />
<b>Cursor:</b> علاوه بر کلاس SmsMessage با استفاده از کلاس Cursor هم می‌توان به آرشیو پیامک‌ها دسترسی داشت که با وجود کلاس SmsMessage استفاده از آن جایگاهی نخواهد داشت با این حال در انتهای آموزش به این مورد نیز اشاره مختصری خواهیم کرد.</p>
<h2 id="android-receive-sms-project">ساخت پروژه دریافت SMS در اندروید استودیو</h2>
<p>برای بررسی نحوه دریافت پیامک (SMS) در برنامه نویسی اندروید مطابق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام ReceiveSMS می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.</p>
<h3 id="receive-sms-using-SmsMessage">دریافت پیامک (SMS) توسط SmsMessage</h3>
<p>اگر بخاطر داشته باشید در جلسه <a href="http://android-studio.ir/send-sms-in-android-programming/" target="_blank" rel="noopener">نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید</a> در مانیفست پروژه مجوز ارسال پیامک را تعریف کردیم. در این پروژه لازم است عکس آن یعنی مجوز دریافت SMS را تعریف کنیم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;
</pre>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.receivesms&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.ReceiveSMS&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>در قدم بعد لازم است یک BroadcastReceiver ایجاد کنم. بر خلاف پروژه ارسال SMS که برودکست درون اکتیویتی و به صورت داینامیک رجیستر شد، در این جلسه برودکست را در کلاس مجزا تعریف کرده و همچنین رجیستر یا ثبت آن را به صورت استاتیک و درون Manifest انجام می‌دهم.<br />
یک کلاس با نام SMSBroadcastReceiver به پروژه اضافه کرده و آنرا از BroadcastReceiver مشتق می‌کنم. سپس متد onReceive را اضافه می‌کنم:</p>
<p><span class="filename">SMSBroadcastReceiver.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.receivesms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class SMSBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        
    }
}
</pre>
<p>سپس برودکست رسیور را در مانیفست پروژه ثبت می‌کنم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;receiver android:name=&quot;.SMSBroadcastReceiver&quot;&gt;
    &lt;intent-filter&gt;
        &lt;action android:name=&quot;android.provider.Telephony.SMS_RECEIVED&quot; /&gt;
    &lt;/intent-filter&gt;
&lt;/receiver&gt;
</pre>
<p>کد کامل مانیفست:<br />
<span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.receivesms&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.RECEIVE_SMS&quot; /&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.ReceiveSMS&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
        &lt;receiver android:name=&quot;.SMSBroadcastReceiver&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.provider.Telephony.SMS_RECEIVED&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/receiver&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>یک اکشن با نام android.provider.Telephony.SMS_RECEIVED در مانیفست تعریف کردیم. این اکشن وظیفه بررسی رویدادهای مربوط به دریافت پیامک را در سیستم عامل بر عهده دارد.<br />
قبل از پیاده سازی کلاس SmsMessage قصد دارم صرفا عملکرد برودکست را بررسی کنم و مطمئن شوم action ای که در مانیفست تعریف کردم می‌تواند به دریافت پیامک واکنش نشان دهد. بنابراین در متد onReceive برودکست رسیور صرفا یک پیغام Toast تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public void onReceive(Context context, Intent intent) {

    Toast.makeText(context, &quot;یک پیامک دریافت شد&quot;, Toast.LENGTH_SHORT).show();

}
</pre>
<p>با توجه به اینکه قصد دارم پروژه را روی یک دیوایس بالاتر از اندروید ۶ اجرا کنم، لازم است کدهای مربوط به Runtime Permission را مطابق <a href="http://android-studio.ir/android-runtime-permission/" target="_blank" rel="noopener">آموزش Runtime Permission در اندروید</a> به اکتیویتی اضافه کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.receivesms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private final int SMS_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) {

            requestReceiveSMSpermission();

        } else {

            Toast.makeText(this, &quot;مجوز قبلا دریافت شده&quot;, Toast.LENGTH_SHORT).show();

        }

    }

    private void requestReceiveSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.RECEIVE_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به دریافت پیامکتایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.RECEIVE_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {

                Toast.makeText(this, &quot;مجوز تایید شد&quot;, Toast.LENGTH_SHORT).show();

            } else {

                Toast.makeText(this, &quot;مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();

            }

        }

    }

}
</pre>
<p>یکی از دیوایس‌های <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">امولاتور اندرویدی Genymotion</a> را اجرا می‌کنم. یکی از امکانات کاربردی شبیه سازهای اندرویدی از جمله Genymotion و همچنین AVD (شبیه ساز داخلی اندروید استودیو) قابلیت ارسال پیامک به دستگاه و همچنین برقراری تماس است. بدون آنکه نیاز به اجرا و تست برنامه روی یک دستگاه واقعی داشته باشیم.<br />
پروژه را روی شبیه ساز اجرا کرده و مجوز دسترسی به پیامک‌ها را برای برنامه تایید می‌کنم. سپس روی آیکون Phone منوی سمت راست دیوایس کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/confirm_RECEIVE_SMS_permission.png" alt="تایید مجوز دسترسی به پیامک‌ها در اندروید" /><figcaption>تایید مجوز دسترسی به پیامک‌ها در اندروید</figcaption></figure>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/send_sms_in_android_emulator.png" alt="قابلیت ارسال SMS در امولاتور Genymotion" /><figcaption>قابلیت ارسال SMS در امولاتور Genymotion</figcaption></figure>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/send_sms_in_android_emulator_2.png" alt="ارسال پیامک آزمایشی در شبیه ساز اندروید" /><figcaption>ارسال پیامک آزمایشی در شبیه ساز اندروید</figcaption></figure>
<p>در کادر Phone یک شماره فرستنده و متن دلخواه وارد کرده و پیام را ارسال می‌کنم. بلافاصله پیغام Toast ای که در متد onReceive تعریف کرده بودم اجرا شد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/sent_sms_received_by_BroadcastReceiver.png" alt="دریافت رویداد پیامک توسط BroadcastReceiver" /><figcaption>دریافت رویداد پیامک توسط BroadcastReceiver</figcaption></figure>
<p>تا اینجای کار مطمئن شدیم برنامه ما می‌تواند به درستی رویدادهای مربوط به دریافت پیامک را شنود کند. در قدم بعد می‌خواهیم محتوای SMS دریافتی را به دست بیاوریم که شامل شماره ارسال کننده پیام و همچنین متن پیامک می‌باشد.<br />
پیغام Toast داخل متد onReceive برودکست رسیور را حذف کرده و کد زیر را جایگزین آن می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
if (intent.getAction().equals(&quot;android.provider.Telephony.SMS_RECEIVED&quot;)) {

    Bundle mBundle = intent.getExtras();
    SmsMessage&#x5B;] msg;
    String smsFrom;

    if (mBundle != null) {
        try {
            Object&#x5B;] mPdus = (Object&#x5B;]) mBundle.get(&quot;pdus&quot;);
            msg = new SmsMessage&#x5B;mPdus.length];

            for (int i = 0; i &lt; mPdus.length; i++) {
                msg&#x5B;i] = SmsMessage.createFromPdu((byte&#x5B;]) mPdus&#x5B;i]);
                smsFrom = msg&#x5B;i].getOriginatingAddress();
                String smsBody = msg&#x5B;i].getMessageBody();

                Toast.makeText(context, &quot;شماره: &quot; + smsFrom + &quot; / پیام: &quot; + smsBody, Toast.LENGTH_SHORT).show();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
</pre>
<p>در ابتدا یک if تعریف شده که بررسی می‌کند کدها تنها در صورتی اجرا شوند که <a href="http://android-studio.ir/intent/" target="_blank" rel="noopener">اینتنت اکشن</a> دریافتی از نوع android.provider.Telephony.SMS_RECEIVED باشد. یعنی دقیقا همان اکشنی که قبلا در AndroidManifest.xml و در تگ receiver ثبت کردیم.<br />
سپس یک نمونه از Bundle و SmsMessage و یک String ساخته شده. در ادامه ابتدا بررسی می‌شود آیا مقدار ذخیره شده در mBundle نال است یا نه؛ یعنی داده‌ای که مربوط به محتوای یک پیامک باشد در باندل ذخیره شده یا خیر.<br />
یک نمونه از <span dir="ltr">Object[]</span> با نام دلخواه mPdus ایجاد شده که داده‌ها با فرمت PDU (مخفف Protocol Data Unit) در آن ذخیره می‌شود. با استفاده از PDU به ساختار محتوای پیام دریافتی دسترسی داریم.<br />
سپس در حلقه for با استفاده از <span dir="ltr">SmsMessage.createFromPdu()</span> محتوای پیام دریافتی تفکیک می‌شود. همانطور که ملاحظه می‌کنید در دو خط بعد توسط getOriginatingAddress و getMessageBody به ترتیب به شماره ارسال کننده پیام و متن پیام دسترسی خواهیم داشت. کاربرد متدها از نحوه نامگذاری آنها هم مشخص است. عبارت Originating Address به معنی آدرس مبدا و Message Body به معنی بدنه یا متن پیام است.<br />
در انتها، این دو آیتم در یک پیغام Toast قرار داده شده است.<br />
کد کامل کلاس برودکست رسیور:</p>
<p><span class="filename">SMSBroadcastReceiver.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.receivesms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class SMSBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        // Toast.makeText(context, &quot;یک پیامک دریافت شد&quot;, Toast.LENGTH_SHORT).show();

        if (intent.getAction().equals(&quot;android.provider.Telephony.SMS_RECEIVED&quot;)) {

            Bundle mBundle = intent.getExtras();
            SmsMessage&#x5B;] msg;
            String smsFrom;

            if (mBundle != null) {
                try {
                    Object&#x5B;] mPdus = (Object&#x5B;]) mBundle.get(&quot;pdus&quot;);
                    msg = new SmsMessage&#x5B;mPdus.length];

                    for (int i = 0; i &lt; mPdus.length; i++) {
                        msg&#x5B;i] = SmsMessage.createFromPdu((byte&#x5B;]) mPdus&#x5B;i]);
                        smsFrom = msg&#x5B;i].getOriginatingAddress();
                        String smsBody = msg&#x5B;i].getMessageBody();

                        Toast.makeText(context, &quot;شماره: &quot; + smsFrom + &quot; / پیام: &quot; + smsBody, Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }

    }
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در کد فوق، متد createFromPdu دو پارامتر ورودی به صورت</p>
<p style="text-align: left; direction: ltr;">
createFromPdu(byte[] pdu, String format)
</p>
<p>می‌گیرد که تا قبل از API 23 دارای یک پارامتر ورودی و به صورت </p>
<p style="text-align: left; direction: ltr;">
createFromPdu(byte[] pdu)
</p>
<p>بوده است. اما نیازی به تعریف هردو متد بر اساس نسخه اندروید کاربر نیست. من پروژه را بر روی اندروید ۴ به بالا تست کردم و اختلالی در دریافت SMS در نسخه‌های قدیمی وجود نداشت.
</p></div>
<p>پروژه را دوباره اجرا کرده و با استفاده از گزینه Phone یک پیام جدید ارسال می‌کنم. حالا شماره ارسال کننده و متن SMS هم در Toast نمایش داده می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/receivesms/get_SMS_OriginatingAddress_&#038;_MessageBody.png" alt="دریافت شماره مبدا و متن پیامک توسط SmsMessage" /><figcaption>دریافت شماره مبدا و متن پیامک توسط SmsMessage</figcaption></figure>
<h3 id="receive-sms-using-Cursor">دریافت پیامک (SMS) توسط Cursor</h3>
<p>همانطور که در ابتدای جلسه اشاره شد، با وجود کلاس SmsMessage استفاده از Cursor برای دریافت پیامک توجیهی نخواهد داشت اما لازم دانستم حداقل برای تمرین به این مورد هم اشاره کنم.<br />
یک پروژه جدید بسازید و TextView پیش فرض اکتیویتی را به Button تبدیل کنید. سپس کد زیر را در رویداد onClickListener دکمه قرار دهید (از تعریف Runtime Permission فراموش نکنید).</p>
<pre class="brush: java; title: ; notranslate">
Cursor cursor = getContentResolver().query(Uri.parse(&quot;content://sms&quot;), null, null, null, null);

cursor.moveToFirst();

Toast.makeText(this, cursor.getString(12), Toast.LENGTH_SHORT).show();
</pre>
<p>بعد از اجرای پروژه و تایید مجوز دریافت SMS، مانند قبل یک پیامک ارسال کرده و سپس روی Button کلیک کنید. متن پیامک در قالب Toast نمایش داده خواهد شد. البته در این روش هم می‌توان شماره ارسال کننده پیامک را دریافت کرد که به آن نمی‌پردازم.</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۱۳<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/14_2_ReceiveSMS.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/14_2_ReceiveSMS.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/receive-sms-in-android-programming/">دریافت پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/receive-sms-in-android-programming/feed/</wfw:commentRss>
			<slash:comments>15</slash:comments>
		
		
			</item>
		<item>
		<title>ارسال پیامک (SMS) در برنامه اندرویدی + سورس پروژه</title>
		<link>https://android-studio.ir/send-sms-in-android-programming/</link>
					<comments>https://android-studio.ir/send-sms-in-android-programming/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 04 May 2021 17:18:52 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=196576</guid>

					<description><![CDATA[<p>یکی از قابلیت‌هایی که در برخی از برنامه‌های اندرویدی مورد استفاده قرار می‌گیرد، ارسال پیامک است. مواردی مانند ارسال داده‌های کم حجم، ارسال موقعیت جغرافیایی و&#8230; از کاربردهای این قابلیت بشمار می‌رود. در این جلسه به نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید می‌پردازیم. همچنین بررسی وضعیت سیم کارت (فعال یا غیر فعال بودن [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/send-sms-in-android-programming/">ارسال پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/send_sms_in_android_programming.png" alt="آموزش ارسال پیامک (SMS) در برنامه نویسی اندروید" /><br />
یکی از قابلیت‌هایی که در برخی از برنامه‌های اندرویدی مورد استفاده قرار می‌گیرد، ارسال پیامک است. مواردی مانند ارسال داده‌های کم حجم، ارسال موقعیت جغرافیایی و&#8230; از کاربردهای این قابلیت بشمار می‌رود. در این جلسه به نحوه ارسال پیامک (SMS) در برنامه نویسی اندروید می‌پردازیم.<br />
همچنین بررسی وضعیت سیم کارت (فعال یا غیر فعال بودن و&#8230;) و دریافت گزارش ارسال و گزارش تحویل پیامک از دیگر مواردی است که در این جلسه به آن پرداخته خواهد شد.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#send-sms-from-android-app">کاربردهای ارسال پیامک از طریق اپلیکیشن</a></li>
<li><a href="#how-to-send-sms-in-android-app">نحوه ارسال پیامک در اندروید</a>
<ul>
<li><a href="#send-sms-using-intent">ارسال پیامک (SMS) از طریق Intent</a></li>
<li><a href="#send-sms-using-smsMamager">ارسال پیامک (SMS) از طریق کلاس SmsManager</a></li>
</ul>
</li>
<li><a href="#android-send-sms-project">ساخت پروژه اندرویدی ارسال پیامک</a>
<ul>
<li><a href="#send-sms-permission">تعریف مجوز ارسال پیامک</a></li>
<li><a href="#smsManager-class">پیاده سازی کلاس SmsManager</a></li>
<li><a href="#send-&#038;-delivery-report-in-smsManager">دریافت گزارش ارسال و گزارش تحویل پیامک</a></li>
<li><a href="#check-sim-state-before-sending-sms">بررسی وضعیت سیم کارت قبل از ارسال پیامک</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="send-sms-from-android-app">کاربردهای ارسال پیامک از طریق اپلیکیشن</h2>
<p>به نام خدا. ارسال پیامک از طریق برنامه‌های اندرویدی کاربردهای متفاوتی می‌تواند داشته باشد. برای مثال در برخی از همراه بانک‌ها کاربر امکان انتخاب اینترنت یا پیامک (SMS) را به عنوان بستر ارسال و دریافت اطلاعات در اختیار دارد.<br />
همچنین یکی دیگر از کاربردهای ارسال پیامک (SMS) در برنامه نویسی اندروید ارسال موقعیت جغرافیایی فرد (بر اساس مختصات GPS) به یک شماره خاص است. قابلیتی که به فرد این امکان را می‌دهد تا در شرایط اضطراری، با لمس یک دکمه موقعیت جغرافیایی خود را برای شماره موبایلی که قبلا در برنامه تعریف کرده ارسال کند. یا برنامه‌ای که بر روی دستگاه اندرویدی کودکان نصب می‌شود تا در فاصله‌های زمانی معین، مختصات جغرافیایی او را به صورت خودکار به شماره همراه والدینش ارسال می‌کند.<br />
ارسال پیامک به مرکز سرویس دهنده‌ی اپلیکیشن به جهت تائید شماره همراه کاربر در هنگام عضویت یکی دیگر از کاربردهای این قابلیت است.<br />
قابلیت ارسال پیامک از اپلیکیشن کاربردهای متعدد دیگری می‌تواند داشته باشد که استفاده از آن به ماهیت برنامه بستگی دارد.</p>
<h2 id="how-to-send-sms-in-android-app">نحوه ارسال پیامک در اندروید</h2>
<p>ارسال پیامک (SMS) در برنامه نویسی اندروید به دو طریق امکان پذیر است. روش نخست استفاده از intent است که در صورت پیاده سازی این روش در برنامه، کاربر برای ارسال پیامک به برنامه مدیریت پیامک پیش فرض دستگاه خود منتقل خواهد شد.<br />
اما در روش دوم با استفاده از کلاس SmsManager فرایند ارسال پیامک درون خود برنامه و در پشت صحنه انجام شده و کاربر از جابجایی بین برنامه اصلی و برنامه مدیریت SMS خود بی نیاز خواهد بود. علاوه بر آن دسترسی به محتوای پیامک و امکان ویرایش آن نیز مقدور نیست.<br />
در ادامه جلسه به بررسی هردو روش می‌پردازیم.</p>
<h3 id="send-sms-using-intent">ارسال پیامک (SMS) از طریق Intent</h3>
<p>قبلا در جلسه <a href="http://android-studio.ir/intent/" target="_blank" rel="noopener">آموزش کار با intent در اندروید</a> با کاربرد اینتنت‌ها در برنامه نویسی اندروید آشنا شدیم. ضمن اینکه در همان جلسه یکی از مثال‌های کاربرد intent به قابلیت ارسال پیامک از طریق برنامه مدیریت SMS پیش فرض دستگاه اختصاص یافته بود که توضیحات کامل را می‌توانید مطالعه کنید. بنابراین از پرداختن مجدد به توضیحات این روش اجتناب می‌کنم و صرفا جهت یاداوری، کد اینتنت مربوط به ارسال پیامک از طریق برنامه مدیریت پیامک را در اینجا ذکر می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public void contactMessage(View v) {
    Intent cnt_msg = new Intent();
    cnt_msg.setAction(Intent.ACTION_VIEW);
    cnt_msg.setData(Uri.parse(&quot;sms:+989158888888&quot;));
    cnt_msg.putExtra(&quot;sms_body&quot;, &quot;سلام. در خصوص اپلیکیشن شرکتتون یه پیشنهاد داشتم&quot;);
    startActivity(cnt_msg);
}
</pre>
<p>با اجرای تابع فوق، کاربر به برنامه پیامک پیش فرض دیوایس اندرویدی منتقل شده و شماره‌ی گیرنده پیام و همچنین متن پیامک به صورت خودکار اضافه می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/send_sms_in_android_using_intent.png" alt="ارسال پیامک (SMS) در اندروید توسط intent" /><figcaption>ارسال پیامک (SMS) در اندروید توسط intent</figcaption></figure>
<p>در اینجا کاربر در صورت تمایل قبل از ارسال پیامک می‌تواند محتوای پیام را ویرایش کند.<br />
اما این شیوه ارسال پیامک به دلیل طولانی بودن فرایند ارسال و همچنین دسترسی کاربر به محتوای آن کاربرد چندانی نداشته و برنامه نویسان اندرویدی عموما روش دوم را بکار می‌گیرند که در ادامه مبحث معرفی می‌کنم.</p>
<h3 id="send-sms-using-smsMamager">ارسال پیامک (SMS) از طریق کلاس SmsManager</h3>
<p>بر خلاف روش قبل یعنی ارسال پیامک از طریق intent در این روش و با استفاده از کلاس SmsManager پیامک به طور مستقیم و درون خود برنامه به مقصد ارسال شده و کاربر دسترسی به محتوای پیامک نخواهد داشت. به عبارت دیگر، کاربر از فرایند ارسال پیامک آگاه نخواهد شد.<br />
با استفاده از SmsManager می‌توان ۴ نوع پیام را ارسال کرد که لازم است کاربرد هرکدام را توضیح دهم:<br />
<b>sendTextMessage:</b> همانطور که از نام این متد پیداست، برای ارسال پیامک (SMS) معمولی بکار می‌رود. یعنی ارسال پیامک متنی در قالب یک پیام و به مقصد از قبل تعیین شده. درست همان چیزی که در این جلسه نیاز داریم.<br />
<b>sendMultipartTextMessage:</b> عملیات مشابه متد قبل را انجام می‌دهد با این تفاوت که می‌توان پیام را در قالب چند پیام جداگانه به مقصد ارسال کرد.<br />
<b>sendDataMessage:</b> از این متد برای ارسال پیام داده محور و در یک پورت مشخص استفاده می‌شود.<br />
<b>sendMultimediaMessage:</b> چنانچه قصد ارسال پیام مولتی مدیا یا همان MMS (مانند ارسال عکس) داشته باشیم باید از این متد استفاده کنیم.</p>
<p>همانطور که در توضیحات قبل اشاره شد، در این جلسه می‌خواهیم یک پیامک متنی ساده و تک قسمتی را ارسال کنیم که متد sendTextMessage مناسب این کار است. با ادامه جلسه همراه باشید.</p>
<h2 id="android-send-sms-project">ساخت پروژه اندرویدی ارسال پیامک</h2>
<p>مطابق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام SendSMS می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
در این پروژه قصد داریم با استفاده از کلاس SmsManager بصورت درون برنامه‌ای و بدون نیاز به برنامه مدیریت پیامک، یک پیام کوتاه به مقصدی مشخص ارسال کنیم. همچنین نحوه فعالسازی گزارش ارسال (sent) و گزارش تحویل (delivery) پیامک و در دسترس بودن یا نبودن سیم کارت جهت ارسال SMS را نیز بررسی خواهیم کرد.<br />
در رابط کاربری این پروژه تنها به یک دکمه برای ارسال متن پیامک از پیش تعیین شده نیاز داریم. بنابراین در layout پیش فرض اکتیویتی پروژه کافیست TextView موجود در مرکز ویو گروپ ConstraintLayout را به Button تبدیل کرده و یک id به آن اختصاص دهم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/sms_btn&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;ارسال پیامک&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>اگر با این layout آشنایی ندارید <a href="http://android-studio.ir/constraintlayout/" target="_blank" rel="noopener">آموزش کار با ConstraintLayout</a> را مطالعه کنید. حالا رویداد مربوط به دکمه را در اکتیویتی تعریف می‌کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

        });

    }
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در نسخه‌های اخیر اندروید استودیو، هنگام اضافه کردن متد setOnClickListener پیشنهادی مبنی بر جایگزینی View.OnClickListener با lambda داده می‌شود که با زدن alt+Enter یا کلیک روی آیکون لامپ، جایگزینی انجام می‌شود. تفاوت در کوتاه شدن کدهاست.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/replace_OnClickListener_with_lambda.png" alt="تبدیل setOnClickListener به lambda" /><figcaption>تبدیل setOnClickListener به lambda</figcaption></figure>
<p>پس از تبدیل، کد رویداد دکمه به اینصورت تبدیل می‌شود:</p>
<pre class="brush: java; title: ; notranslate">
sendBtn.setOnClickListener(view -&gt; {

});
</pre>
</div>
<h3 id="send-sms-permission">تعریف مجوز ارسال پیامک</h3>
<p>در قدم اول لازم است مجوز یا Permission ارسال پیامک (SMS) را در برنامه تعریف کنیم. ابتدا پرمیشن SEND_SMS را در مانیفست پروژه تعریف می‌کنم:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;uses-permission android:name=&quot;android.permission.SEND_SMS&quot;/&gt;
</pre>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.sendsms&quot;&gt;

    &lt;uses-permission android:name=&quot;android.permission.SEND_SMS&quot;/&gt;

    &lt;application
        android:allowBackup=&quot;true&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;
        android:label=&quot;@string/app_name&quot;
        android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;
        android:supportsRtl=&quot;true&quot;
        android:theme=&quot;@style/Theme.SendSMS&quot;&gt;
        &lt;activity android:name=&quot;.MainActivity&quot;&gt;
            &lt;intent-filter&gt;
                &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;

                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;

&lt;/manifest&gt;
</pre>
<p>همچنین مطابق جلسه <a href="http://android-studio.ir/android-runtime-permission/" target="_blank" rel="noopener">آموزش پیاده سازی Runtime Permission</a> برای دریافت مجوز در دستگاه‌های اندرویدی ۶ و به بالا کدهای لازم را به پروژه اضافه می‌کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;
    private final int SMS_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
                
                requestSendSMSpermission();
                
            } else {
                
                sendMessage();
                
            }

        });

    }

    private void requestSendSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.SEND_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به ارسال پیامک تایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.SEND_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {
                
                sendMessage();
                
            } else {
                
                Toast.makeText(this, &quot;مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();
                
            }

        }

    }
    
}
</pre>
<h3 id="smsManager-class">پیاده سازی کلاس SmsManager</h3>
<p>با اضافه شدن کدهای فوق، پس از لمس دکمه &#8220;ارسال پیامک&#8221; چنانچه کاربر مجوز ارسال SMS را به برنامه اعطا کند، تابع sendMessage فراخوانی خواهد شد. تابعی با همین نام درون اکتیویتی و بعد از متد onCreate اضافه کرده و کدهای مربوط به ارسال پیامک را درون آن می‌نویسم:</p>
<pre class="brush: java; title: ; notranslate">
private void sendMessage() {

    try {
        
        SmsManager smsManager = SmsManager.getDefault();
        smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot;تست ارسال پیامک&quot;, null, null);

        Toast.makeText(MainActivity.this, &quot; پیامک ارسال شد&quot;, Toast.LENGTH_SHORT).show();
        
    } catch (Exception e) {
        
        Toast.makeText(MainActivity.this, &quot; پیامک ارسال نشد&quot;, Toast.LENGTH_SHORT).show();
        
    }

}
</pre>
<p>کدهای مربوط به ارسال پیامک را درون یک try catch قرار داده‌ام تا چنانچه به هر دلیلی فرایند ارسال SMS انجام نشد، برنامه کرش نکند.<br />
در کد فوق ابتدا یک نمونه (شیء) از کلاس SmsManager با نام دلخواه smsManager ایجاد شده که برای مقدار دهی آن، متد getDefault فراخوانی شده است. این متد شناسه سیم کارت پیش فرض دستگاه اندرویدی را دریافت می‌کند که برای ارسال پیامک ضروری است. توصیه می‌کنم توضیحات تکمیلی <a href="https://developer.android.com/reference/android/telephony/SmsManager#sendTextMessage(java.lang.String,%20java.lang.String,%20java.lang.String,%20android.app.PendingIntent,%20android.app.PendingIntent)" target="_blank" rel="noopener">اینجا</a> و <a href="https://developer.android.com/reference/android/telephony/SmsManager#getDefault()" target="_blank" rel="noopener">اینجا</a> را در خصوص دستگاه‌های دو سیم کارت مطالعه کنید.<br />
در خط دوم، متد sendTextMessage تعریف شده که ۴ پارامتر ورودی می‌پذیرد. در تصویر زیر جنس هر ورودی قابل تشخیص است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sendTextMessage_method_parameters.png" alt="پارامترهای ورودی sendTextMessage" /><figcaption>پارامترهای ورودی sendTextMessage</figcaption></figure>
<p><b>destinationAddress:</b> پارامتر نخست از جنس String بوده که همان شماره گیرنده پیام می‌باشد.<br />
<b>scAddress:</b> پارامتر دوم مربوط به آدرس یا شماره‌ی Service Center پیامک اپراتوری است که کاربر از سیم کارت آن استفاده می‌کند. به طور معمول این شماره توسط کلاینت تعیین نمی‌شود و به عهده‌ی خود اپراتور است. بنابراین مقدار null را وارد می‌کنیم.<br />
<b>text:</b> واضح است که این پارامتر، محتوای پیام را شامل می‌شود.<br />
<b>sentIntent و deliveryIntent:</b> به ترتیب برای دریافت گزارش ارسال و گزارش تحویل پیامک استفاده می‌شود که در این مرحله به جهت درک بهتر عملکرد اصلی کلاس، برای هردو مقدار null درنظر گرفتم. بنابراین در این مرحله به نتیجه‌ی ارسال و یا تحویل پیامک به مقصد دسترسی نخواهیم داشت.<br />
نگران نباشید! به این دو مورد هم در ادامه جلسه می‌پردازیم.<br />
در همین مرحله پروژه را اجرا می‌کنم تا عملکرد کلاس SmsManager را بررسی کنیم.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/run_send_sms_android_project_on_emulator.png" alt="اجرای پروژه اندرویدی ارسال SMS روی شبیه ساز" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/send_sms_permission_granted.png" alt="تایید مجوز ارسال پیامک" /><figcaption>تایید مجوز ارسال پیامک</figcaption></figure>
<p>با توجه به اینکه یک دیوایس با Android 10 را روی <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">شبیه ساز (امولاتور) اندرویدی</a> اجرا کرده‌ام، با کلیک روی دکمه ارسال پیامک ابتدا باید مجوز ارسال پیامک را تایید کنم.<br />
با تایید مجوز، پیغام &#8220;پیامک ارسال شد&#8221; روی صفحه ظاهر شد که نشان می‌دهد عملکرد برنامه تا اینجای کار درست بوده:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_sent_using_SmsManager_class.png" alt="ارسال پیامک توسط کلاس SmsManager" /><figcaption>ارسال پیامک توسط کلاس SmsManager</figcaption></figure>
<p>پیامک ارسال شده در برنامه مدیریت SMS پیش فرض دستگاه نیز قابل مشاهده است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_in_device_default_sms_app.png" alt="نمایش پیامک ارسال شده توسط SmsManager در برنامه پیامک پیش فرض دستگاه" /><figcaption>نمایش پیامک ارسال شده توسط SmsManager در برنامه پیامک پیش فرض دستگاه</figcaption></figure>
<h3 id="send-&#038;-delivery-report-in-smsManager">دریافت گزارش ارسال و گزارش تحویل پیامک</h3>
<p>در قسمت قبل صرفا یک SMS توسط کلاس SmsManager ارسال کردیم و دسترسی به گزارش ارسال و تحویل پیامک نداشتیم و برای هردو پارامتر sentIntent و deliveryIntent مقدار null داده بودیم.<br />
اما در این قسمت و در ادامه‌ی آموزش ارسال پیامک (SMS) در برنامه نویسی اندروید با استفاده از BroadcastReceiver و PendingIntent رویدادهای مربوط به وضعیت ارسال و تحویل پیامک را شنود کرده و پیغام مربوط آن را چاپ می‌کنیم.<br />
ابتدا در بدنه اصلی کلاس اکتیویتی دو متغیر از جنس String با نام و مقدار دلخواه تعریف می‌کنم. از این دو متغیر برای برقراری ارتباط بین BroadcastReceiver و PendingIntent مربوط به یکدیگر استفاده خواهیم کرد:</p>
<pre class="brush: java; title: ; notranslate">
String SMS_SENT = &quot;SMS_SENT&quot;;
String SMS_DELIVERED = &quot;SMS_DELIVERED&quot;;
</pre>
<p>سپس مطابق جلسه <a href="http://android-studio.ir/android-broadcastreceiver-component/" target="_blank" rel="noopener">آموزش شنود رویدادها در اندروید توسط BroadcastReceiver</a> دو برودکست را درون متد sendMessage داخل بلاک try و قبل از smsManager، رجیستر می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
//Broadcast for Sent SMS
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {

        String state = &quot;&quot;;
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                state = &quot;پیامک ارسال شد&quot;;
                break;
            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                state = &quot;یک خطای عمومی رخ داد&quot;;
                break;
            case SmsManager.RESULT_ERROR_NO_SERVICE:
                state = &quot;اپراتور در دسترس نیست&quot;;
                break;
            case SmsManager.RESULT_ERROR_NULL_PDU:
                state = &quot; پروتکل PDU در دسترس نیست&quot;;
                break;
            case SmsManager.RESULT_ERROR_RADIO_OFF:
                state = &quot;سیم کارت در دسترس نیست&quot;;
                break;
        }
        Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
    }
}, new IntentFilter(SMS_SENT));

//Broadcast for Delivered SMS
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String state = &quot;&quot;;
        switch (getResultCode()) {
            case Activity.RESULT_OK:
                state = &quot;پیامک تحویل داده شد&quot;;
                break;
            case Activity.RESULT_CANCELED:
                state = &quot;پیامک تحویل داده نشد&quot;;
                break;
        }
        Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
    }
}, new IntentFilter(SMS_DELIVERED));
</pre>
<p>برودکست اول برای شنود وضعیت ارسال پیامک استفاده خواهد شد. چنانچه پیامک با موفقیت به سرویس دهنده (اپراتور) ارسال شود، پیغام &#8220;پیامک ارسال شد&#8221; و در غیر اینصورت سایر پیغام‌های مناسب وضعیت رخ داده نمایش داده خواهد شد.<br />
برای حالت عدم ارسال پیامک، ۴ حالت مختلف تعریف شده که با توجه به وضعیت دریافتی از SmsManager، نوع خطا را به کاربر اعلام می‌کند. خطای دریافتی می‌تواند یک خطای عمومی بوده یا مربوط به در دسترس نبودن اپراتور، در دسترس نبودن سیم کارت و یا پروتکل PDU (Protocol Data Unit) باشد.<br />
برودکست دوم نیز برای شنود وضعیت تحویل یا عدم تحویل SMS به گیرنده بکار می‌رود.<br />
بعد از برودکست‌ها دو PendingIntent به صورت زیر تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
PendingIntent sentSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
PendingIntent deliverSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);
</pre>
<p>قبلا در جلسه <a href="http://android-studio.ir/advanced-android-notifications/" target="_blank" rel="noopener">آموزش ساخت نوتیفیکیشن‌های حرفه‌ای در اندروید</a> با کاربرد PendingIntent ها آشنا شدیم. با استفاده از متد getBroadcast، برودکست موردنظر در PendingIntent تعریف می‌شود.<br />
پارامتر سوم getBroadcast از جنس اینتنت بوده که شامل اکشن‌های SMS_SENT و SMS_DELIVERED می‌باشد که قبلا در برودکست‌ها تعیین کرده بودیم. همانطور که از نحوه نامگذاری PendingIntent ها پیداست، مورد اول برای اجرای برودکست ارسال پیامک و مورد دوم برای اجرای برودکست تحویل پیامک استفاده خواهد شد.<br />
در انتها لازم است این دو PendingIntent در smsManager جایگزین مقدارهای null شوند:</p>
<pre class="brush: java; title: ; notranslate">
smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot;تست ارسال پیامک&quot;, sentSMS, deliverSMS);
</pre>
<p>بنابراین با ارسال پیامک توسط SmsManager در آینده و هر زمانی که گزارش مربوط به وضعیت ارسال و همچنین تحویل پیامک توسط سیستم عامل منتشر شود، توسط BroadcastReceiver ها دریافت شده و پیغام مناسب روی صفحه ظاهر خواهد شد.<br />
در نهایت، متن پیغام Toast که بعد از smsManager قرار گرفته را از &#8220;پیامک ارسال شد&#8221; به &#8220;ارسال پیامک آغاز شد&#8221; تغییر می‌دهم تا از پیغامی که در برودکست ارسال پیامک تعریف شده قابل تشخیص باشد.<br />
مجدد پروژه را اجرا کرده و روی دکمه ارسال پیامک کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_is_sending.png" alt="ارسال SMS توسط SmsManager در برنامه نویسی اندروید" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_sent_report_using_sentIntent.png" alt="گزارش ارسال پیامک توسط sentIntent" /><figcaption>گزارش ارسال پیامک توسط sentIntent</figcaption></figure>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/sms_delivery_report_using_deliveryIntent.png" alt="گزارش تحویل پیامک توسط deliveryIntent" /><figcaption>گزارش تحویل پیامک توسط deliveryIntent</figcaption></figure>
<p>مشاهده می‌کنید گزارش مربوط به ارسال و تحویل پیامک توسط برنامه دریافت و پیغام آن نمایش داده شد.<br />
کد نهایی اکتیویتی تا اینجای کار:</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در صورت نیاز به کپی کدها بهتر است از فایل سورس کد ضمیمه آموزش استفاده کنید.
</div>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;
    private final int SMS_REQUEST_CODE = 100;
    String SMS_SENT = &quot;SMS_SENT&quot;;
    String SMS_DELIVERED = &quot;SMS_DELIVERED&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {

                requestSendSMSpermission();

            } else {

                sendMessage();

            }

        });

    }

    private void sendMessage() {

        try {

            //Broadcast for Sent SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {

                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک ارسال شد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                            state = &quot;یک خطای عمومی رخ داد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NO_SERVICE:
                            state = &quot;اپراتور در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NULL_PDU:
                            state = &quot; پروتکل PDU در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_RADIO_OFF:
                            state = &quot;سیم کارت در دسترس نیست&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_SENT));

            //Broadcast for Delivered SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک تحویل داده شد&quot;;
                            break;
                        case Activity.RESULT_CANCELED:
                            state = &quot;پیامک تحویل داده نشد&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_DELIVERED));

            PendingIntent sentSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
            PendingIntent deliverSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

            SmsManager smsManager = SmsManager.getDefault();
            smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot; تست ارسال پیامک&quot;, sentSMS, deliverSMS);

            Toast.makeText(MainActivity.this, &quot; ارسال پیامک آغاز شد&quot;, Toast.LENGTH_SHORT).show();

        } catch (Exception e) {

            Toast.makeText(MainActivity.this, &quot; پیامک ارسال نشد&quot;, Toast.LENGTH_SHORT).show();

        }

    }

    private void requestSendSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.SEND_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به ارسال پیامک تایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.SEND_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {

                sendMessage();

            } else {

                Toast.makeText(this, &quot; مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();

            }

        }

    }

}
</pre>
<h3 id="check-sim-state-before-sending-sms">بررسی وضعیت سیم کارت قبل از ارسال پیامک</h3>
<p>در قسمت قبل پس از ارسال پیامک و توسط کلاس SmsManager وضعیت سیم کارت بررسی می‌شد و در صورت عدم امکان ارسال SMS علت عدم ارسال توسط BroadcastReceiver مشخص می‌شد.<br />
اما با استفاده از کلاس TelephonyManager و متد getSimState می‌توانیم قبل از اقدام به ارسال SMS هم وضعیت سیم کارت را از سیستم عامل دریافت کرده و بر اساس آن عملیاتی انجام داد. برای مثال تا زمانی که سیم کارت در دسترس نباشد، دکمه ارسال پیامک در حالت غیر فعال (Disable) قرار گیرد و یا سایر روش‌های آگاه سازی کاربر به در دسترس نبودن سیم کارت که موضوع این جلسه نیست.<br />
البته کاربرد getSimState محدود به قبل از ارسال پیامک نمی‌شود. برنامه ما ممکن است به هر دلیلی نیاز به فعال بودن سیم کارت داشته باشد؛ مانند دریافت رمز یکبار مصرف. بنابراین می‌توانیم کاربر را از وضعیت سیم کارت دستگاه مطلع کنیم.<br />
یک متد با نام دلخواه simState درون اکتیویتی تعریف و به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
public void simState() {
    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    int SIM_STATE = telephonyManager.getSimState();

    if (SIM_STATE == TelephonyManager.SIM_STATE_READY) {
        Toast.makeText(this, &quot;سیم کارت در دسترس است&quot;, Toast.LENGTH_SHORT).show();
    }
    else {
        String state = &quot;&quot;;
        switch (SIM_STATE) {
            case TelephonyManager.SIM_STATE_ABSENT:
                state = &quot;Sim absent&quot;;
                break;
            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
                state = &quot;Network locked&quot;;
                break;
            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                state = &quot;Pin required&quot;;
                break;
            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                state = &quot;PUK required&quot;;
                break;
            case TelephonyManager.SIM_STATE_UNKNOWN:
                state = &quot;Unknown&quot;;
                break;
        }
        Toast.makeText(this, state, Toast.LENGTH_SHORT).show();
    }
}
</pre>
<p>در کد فوق با استفاده از متد getSimState وضعیت سیم کارت بررسی می‌شود. چنانچه سیم کارت در دسترس باشد حالت SIM_STATE_READY برگردانده می‌شود. در غیر اینصورت یکی از ۵ حالت تعریف شده در قسمت دوم شرط برگردانده شده و پیغام مربوط به آن حالت در Toast نمایش داده خواهد شد.<br />
برای اجرای متد <span dir="ltr">simState()</span> کافیست آنرا در متد <span dir="ltr">onCreate()</span> اکتیویتی فراخوانی کنیم.<br />
پروژه را یکبار در حالتی که سیم کارت درون شیار دستگاه قرار دارد و بار دوم در حالتی که سیم کارت موجود نیست اجرا می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/get_sim_state_using_TelephonyManager_getSimState.png" alt="دریافت وضعیت سیم کارت توسط متد getSimState کلاس TelephonyManager" /><figcaption>دریافت وضعیت سیم کارت توسط متد getSimState کلاس TelephonyManager </figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sendsms/get_sim_state_using_TelephonyManager_getSimState_2.png" alt="دریافت وضعیت سیم کارت توسط متد getSimState کلاس TelephonyManager در اندروید" /></p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.sendsms;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    Button sendBtn;
    private final int SMS_REQUEST_CODE = 100;
    String SMS_SENT = &quot;SMS_SENT&quot;;
    String SMS_DELIVERED = &quot;SMS_DELIVERED&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

	simState();

        sendBtn = findViewById(R.id.sms_btn);

        sendBtn.setOnClickListener(view -&gt; {

            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {

                requestSendSMSpermission();

            } else {

                sendMessage();

            }

        });

    }

    private void sendMessage() {

        try {

            //Broadcast for Sent SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {

                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک ارسال شد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
                            state = &quot;یک خطای عمومی رخ داد&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NO_SERVICE:
                            state = &quot;اپراتور در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_NULL_PDU:
                            state = &quot; پروتکل PDU در دسترس نیست&quot;;
                            break;
                        case SmsManager.RESULT_ERROR_RADIO_OFF:
                            state = &quot;سیم کارت در دسترس نیست&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_SENT));

            //Broadcast for Delivered SMS
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    String state = &quot;&quot;;
                    switch (getResultCode()) {
                        case Activity.RESULT_OK:
                            state = &quot;پیامک تحویل داده شد&quot;;
                            break;
                        case Activity.RESULT_CANCELED:
                            state = &quot;پیامک تحویل داده نشد&quot;;
                            break;
                    }
                    Toast.makeText(context, state, Toast.LENGTH_SHORT).show();
                }
            }, new IntentFilter(SMS_DELIVERED));

            PendingIntent sentSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
            PendingIntent deliverSMS = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

            SmsManager smsManager = SmsManager.getDefault();
            smsManager.sendTextMessage(&quot;+989158888888&quot;, null, &quot; تست ارسال پیامک&quot;, sentSMS, deliverSMS);

            Toast.makeText(MainActivity.this, &quot; ارسال پیامک آغاز شد&quot;, Toast.LENGTH_SHORT).show();

        } catch (Exception e) {

            Toast.makeText(MainActivity.this, &quot; پیامک ارسال نشد&quot;, Toast.LENGTH_SHORT).show();

        }

    }

    private void requestSendSMSpermission() {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.SEND_SMS)) {

            new AlertDialog.Builder(this)
                    .setTitle(&quot;درخواست مجوز&quot;)
                    .setMessage(&quot;برای عملکرد صحیح برنامه باید دسترسی به ارسال پیامک تایید شود&quot;)
                    .setPositiveButton(&quot;موافقم&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            reqPermission();

                        }
                    })
                    .setNegativeButton(&quot;لغو&quot;, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {

                            dialogInterface.dismiss();

                        }
                    })
                    .create()
                    .show();

        } else {

            reqPermission();

        }

    }

    private void reqPermission() {

        ActivityCompat.requestPermissions(MainActivity.this, new String&#x5B;] {Manifest.permission.SEND_SMS}, SMS_REQUEST_CODE);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String&#x5B;] permissions, @NonNull int&#x5B;] grantResults) {

        if (requestCode == SMS_REQUEST_CODE) {

            if (grantResults.length &gt; 0 &amp;&amp; grantResults&#x5B;0] == PackageManager.PERMISSION_GRANTED) {

                sendMessage();

            } else {

                Toast.makeText(this, &quot; مجوز رد شد&quot;, Toast.LENGTH_SHORT).show();

            }

        }

    }

    public void simState() {
    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    int SIM_STATE = telephonyManager.getSimState();

    if (SIM_STATE == TelephonyManager.SIM_STATE_READY) {
        Toast.makeText(this, &quot;سیم کارت در دسترس است&quot;, Toast.LENGTH_SHORT).show();
    }
    else {
        String state = &quot;&quot;;
        switch (SIM_STATE) {
            case TelephonyManager.SIM_STATE_ABSENT:
                state = &quot;Sim absent&quot;;
                break;
            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
                state = &quot;Network locked&quot;;
                break;
            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
                state = &quot;Pin required&quot;;
                break;
            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
                state = &quot;PUK required&quot;;
                break;
            case TelephonyManager.SIM_STATE_UNKNOWN:
                state = &quot;Unknown&quot;;
                break;

            }
        	Toast.makeText(this, state, Toast.LENGTH_SHORT).show();
        }
    }

}
</pre>
<p>در این جلسه ارسال پیامک (SMS) در برنامه نویسی اندروید را به همراه نحوه‌ی دریافت گزارش ارسال و تحویل و همچنین وضعیت سیم کارت دیوایس اندرویدی بررسی کردیم. امیدوارم مفید واقع شود.<br />
موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developer.android.com/reference/android/telephony/SmsManager" target="_blank" rel="noopener">https://developer.android.com/reference/android/telephony/SmsManager</a><br />
<a href="https://developer.android.com/reference/android/app/PendingIntent" target="_blank" rel="noopener">https://developer.android.com/reference/android/app/PendingIntent</a>
</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۸<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/14_1_Send_SMS.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/14_1_Send_SMS.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/send-sms-in-android-programming/">ارسال پیامک (SMS) در برنامه اندرویدی + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/send-sms-in-android-programming/feed/</wfw:commentRss>
			<slash:comments>16</slash:comments>
		
		
			</item>
		<item>
		<title>طراحی صفحات با ConstraintLayout + سورس پروژه</title>
		<link>https://android-studio.ir/constraintlayout/</link>
					<comments>https://android-studio.ir/constraintlayout/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Wed, 03 Mar 2021 20:38:10 +0000</pubDate>
				<category><![CDATA[آموزش‌های پایه]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=195516</guid>

					<description><![CDATA[<p>در جلسه قبل با عنوان آموزش طراحی رابط کاربری در اندروید با کلیات رابط کاربری، ویو، ویجت‌ها و همچنین لایه‌ (Layout یا ViewGroup) های پرکاربرد LinearLayout و RelativeLayout آشنا شدیم. در این جلسه قصد داریم طراحی صفحات با ConstraintLayout را بررسی کنیم. این layout که نسبت به سایر layout های اندروید عمر کمتری دارد، از [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/constraintlayout/">طراحی صفحات با ConstraintLayout + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/android_constraintlayout_tutorial.png" alt="آموزش طراحی رابط کاربری اندروید توسط ConstraintLayout" /><br />
در جلسه قبل با عنوان آموزش طراحی رابط کاربری در اندروید با کلیات رابط کاربری، ویو، ویجت‌ها و همچنین لایه‌ (Layout یا ViewGroup) های پرکاربرد LinearLayout و RelativeLayout آشنا شدیم. در این جلسه قصد داریم طراحی صفحات با ConstraintLayout را بررسی کنیم.<br />
این layout که نسبت به سایر layout های اندروید عمر کمتری دارد، از انعطاف بسیار بالاتری برخوردار بوده و می‌تواند زمان طراحی و حجم کدهای xml رابط کاربری اکتیویتی را تا حد زیادی کاهش دهد. در نتیجه با کاهش حجم کدها، سرعت نمایش صفحات افزایش یافته و می‌توانیم شاهد بهبود عملکرد کلی اپلیکیشن باشیم.<br />
در صورتی که با ویژگی‌ها و امکانات  به خوبی آشنا بوده و به اندازه کافی روی این ViewGroup جدید تمرینات مختلف انجام داده باشیم، احتمالا در اکثر موارد می‌توانیم بدون بکارگیری لایه‌های LinearLayout، RelativeLayout، FrameLayout، <a href="http://android-studio.ir/android-tablelayout/">TableLayout</a> و <a href="http://android-studio.ir/android-gridlayout-viewgroup/">GridLayout</a> رابط‌های کاربری مدنظر خود را در بهینه‌ترین حالت ممکن پیاده سازی کنیم.<br />
در این جلسه سعی می‌کنم تمامی قابلیت‌ها و جزئیات این layout تازه نفس را در قالب مثال بیان کنم. توجه داشته باشید این آموزش به نوعی ادامه‌ی جلسه <a href="http://android-studio.ir/android-user-interface/">آموزش طراحی رابط کاربری در اندروید</a> بوده و پیش نیاز این جلسه، آشنایی با مفاهیم ابتدایی رابط کاربری می‌باشد که قبلا به آن‌ها پرداخته‌ایم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#what-is-constraintlayout">ConstraintLayout چیست؟</a></li>
<li><a href="#design-android-ui-by-ConstraintLayout">نحوه طراحی صفحات رابط کاربری اندروید با ConstraintLayout</a>
<ul>
<li><a href="#constraints">خطوط اتصال و گره‌ها در ConstraintLayout</a></li>
<li><a href="#bias">تعیین فاصله از چپ و پایین توسط Bias</a></li>
<li><a href="#width-height">تعیین عرض و ارتفاع Widget یا View ها</a></li>
<li><a href="#clear-all">حذف یکباره تمامی Constraint ها</a></li>
<li><a href="#convert-to-constraintlayout">تبدیل ViewGroup های دیگر به ConstraintLayout</a></li>
<li><a href="#auto-constraint">ایجاد خودکار Constraint ها</a></li>
<li><a href="#margin">تعیین فاصله یا Margin پیش فرض</a></li>
<li><a href="#baseline">همتراز کردن ویجت‌های متنی توسط Baseline</a></li>
<li>همتراز کردن ویجت‌ها در جهت افقی یا عمودی</li>
<li>ایجاد فاصله یکسان بین View ها با استفاده از Chain</li>
<li>پیاده سازی استایل‌های مختلف برای Chain</li>
<li>کاربرد گزینه Autoconnection to Parent</li>
<li>کاربرد گزینه‌های Pack و Expand</li>
<li>اتصال خودکار ویجت‌ها توسط Distribute</li>
<li>کاربرد گزینه‌های Edges</li>
<li>تنظیم خودکار Baseline ها</li>
<li>تراز کردن View ها توسط Guideline یا خط راهنما</li>
<li>تعیین محدوده متغیر برای چند ویجت توسط Barrier</li>
<li>نمایش، حذف یا مخفی کردن چندین View توسط Group</li>
<li>تغییر چیدمان ویجت‌ها بر اساس اندازه صفحه نمایش توسط Flow</li>
<li>جابجایی چند ویجت به صورت گروهی توسط Layer</li>
</ul>
</li>
</ul>
</div>
<p>&nbsp;</p>
<p class="copy">این جلسه در قالب PDF و در ۱۰۷ صفحه تهیه شده که در ادامه چند صفحه‌ ابتدایی را مشاهده می‌کنید:</p>
<p></p>
<h2 id="what-is-constraintlayout">ConstraintLayout چیست؟</h2>
<p>به نام خدا. ConstraintLayout برای اولین بار در کنفرانس Google I/O در سال ۲۰۱۶ معرفی شد. اصلی ترین هدف گوگل از معرفی این Layout جدید، امکان طراحی لایه‌های پیچیده و در عین حال flat (تخت) بود؛ بر خلاف Layout های قبلی که برای طراحی یک صفحه ناگزیر به استفاده از لایه‌های تو در تو و سلسله مراتبی بودیم.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/android_constraintlayout.png" alt="برتری‌های ConstraintLayout نسبت به ViewGroup های قبلی" /></p>
<p>این یعنی دیگر لازم نیست چندین لایه RelativeLayout و LinearLayout و&#8230; درون یکدیگر و به صورت تو در تو تعریف شود تا بتوان یک صفحه چند قسمتی را ساخت. بنابراین علاوه بر سادگی پیاده سازی این لایه و کاهش حجم نهایی کدهای آن، در طراحی صفحات با ConstraintLayout کارایی و performance اکتیویتی بهبود یافته و می‌تواند در افزایش سرعت نمایش UI روی دستگاه کاربر تاثیر مثبت بگذارد.<br />
ConstraintLayout از اندروید استودیو ۲٫۲ اضافه شد و نسخه ابتدایی آن Beta 1 نام داشت. در طی این چند سال مرتبا اصلاحاتی روی آن انجام و همچنین قابلیت‌های جدیدی نیز اضافه شده است. خبر خوب اینکه این لایه از API 9 به بعد پشتیبانی می‌شود بنابراین مطمئن خواهیم بود تمامی دستگاه‌های اندرویدی که در حال حاضر در اختیار افراد هست به خوبی می‌توانند صفحاتی که با این layout طراحی شده‌اند را نمایش دهند.<br />
در ConstraintLayout می‌توانیم وضعیت قرارگیری تمامی view/widget ها را نسبت به یکدیگر و یا parent شان تعیین کنیم. برای مثال می‌توانیم یک TextView را به نحوی در زیر یک ImageView متصل کنیم که در جهت افقی، متن همواره در مرکز تصویر قرار گیرد. یا اینکه یک یا چند view بر اساس سایز صفحه نمایش دستگاه، چه از جهت افقی و چه عمودی همواره در مرکز قرار گرفته و علاوه بر آن، فاصله آنها از یکدیگر نیز درصدی ثابت باشد.<br />
این یعنی یک انعطاف پذیری بسیار بالا که تا حدود زیادی مشابه طراحی صفحات واکنشگرا یا Responsive در صفحات وب است.<br />
فکر می‌کنم هرچه اینجا بیشتر توضیح بدهم بیشتر هم شما را گیج می‌کنم! بنابراین بهتر است سراغ محیط توسعه اندروید استودیو رفته و امکانات این لایه قدرتمند را در عمل بررسی کنیم.<br />
توجه داشته باشید در نسخه‌های ابتدایی اندروید استودیو که ConstraintLayout اضافه شده بود می‌بایست ابزار آن به صورت جداگانه در SDK و در زیرمجموعه Support Repository دریافت و نصب می‌شد اما در نسخه‌های اخیر، این گزینه از لیست SDK Tools حذف شده و با سایر ابزار اصلی توسعه اندروید ادغام شده است.<br />
قبل از هرچیز توجه داشته باشید در برخورد اول قطعا کار با این layout مقداری گیج کننده خواهد بود بنابراین لازم است بارها و بارها تمرین کرده و خروجی کار را مقایسه کنید تا کاربرد هر قابلیت را بتوانید به درستی درک نمائید.</p>
<h2 id="design-android-ui-by-ConstraintLayout">نحوه طراحی صفحات رابط کاربری اندروید با ConstraintLayout</h2>
<p>قبل از هرچیز ابتدا طبق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام ConstraintLayout می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
کتابخانه این layout به صورت پیش فرض در بلاک dependencies فایل build.gradle(app) پروژه اندرویدی قرار دارد:</p>
<pre class="brush: java; title: ; notranslate">
dependencies {
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}
</pre>
<p>در زمان نگارش این آموزش، نسخه ۲٫۰٫۴ جدیدترین نسخه از این ViewGroup است.<br />
Layout پیش فرض اکتیویتی پروژه از نوع ConstraintLayout بوده که در قسمت Component tree قابل مشاهده است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/default_constraintlayout.png" alt="ConstraintLayout پیش فرض در اکتیویتی" /><figcaption>ConstraintLayout پیش فرض در اکتیویتی</figcaption></figure>
<p>محیط پیش نمایش در دو حالت Design و Blueprint قابل نمایش است که به منظور خلوت بودن صفحه در حین آموزش تنها از حالت Design استفاده می‌کنم.</p>
<h3 id="constraints">خطوط اتصال و گره‌ها در ConstraintLayout</h3>
<p>از ساده ترین مثال شروع می‌کنم. ملاحظه می‌کنید یک TextView به طور پیش فرض در مرکز صفحه پیش نمایش با عنوان Hello World! قرار گرفته. موس را روی TextView قرار می‌دهم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/show_constraints_of_views.png" alt="خطوط اتصال Constraint" /><figcaption>خطوط اتصال Constraint</figcaption></figure>
<p>این TextView توسط ۴ خط اتصال به ۴ لبه‌ی صفحه متصل شده است. حالا روی TextView کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/show_constraints_nodes.png" alt="نقاط اتصال در ConstraintLayout" /><figcaption>نقاط اتصال (گره یا نود) در ConstraintLayout</figcaption></figure>
<p>ملاحظه می‌کنید خطوط اتصال پررنگ شده و نقاط اتصال خطوط به ویجت مدنظر نیز به شکل دایره‌های توپر نمایش داده می‌شود. هر کدام از این دایره‌ها را یک گره یا node می‌نامیم. علت قرار گرفتن این TextView در مرکز صفحه این است که هر ۴ گره آن به چهار جهت صفحه نمایش متصل شده.<br />
قبل از انجام هر کاری کد xml صفحه را بررسی می‌کنیم:</p>
<p><span class="filename">activity_main.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;
    tools:context=&quot;.MainActivity&quot;&gt;

    &lt;TextView
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Hello World!&quot;
        app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
        app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
        app:layout_constraintRight_toRightOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>ملاحظه می‌کنید یک TextView با تعدادی ویژگی درون layout از جنس ConstraintLayout قرار گرفته است. به عبارتی این layout والد یا parent ویجت TextView است.</p>
<pre class="brush: xml; title: ; notranslate">
app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
app:layout_constraintLeft_toLeftOf=&quot;parent&quot;
app:layout_constraintRight_toRightOf=&quot;parent&quot;
app:layout_constraintTop_toTopOf=&quot;parent&quot;
</pre>
<p>این چهار attribute موقعیت مکانی TextView را نسبت به والد خود تعیین می‌کند. یعنی به ازاء هر خط اتصالی که از گره‌های ویجت به یکی از کناره‌های صفحه نمایش کشیده شده یکی از این ویژگی‌ها به کد TextView اضافه شده است.</p>
<pre class="brush: xml; title: ; notranslate">
app:layout_constraintBottom_toBottomOf=&quot;parent&quot;
</pre>
<p>اگر به نحوه نامگذاری هر کدام دقت کنید کاربرد آن را متوجه خواهید شد.<br />
Bottom to Bottom of parent یعنی پایینِ TextView به پایینِ parent متصل شود. و به همین ترتیب برای ۳ مورد دیگر. نتیجه این می‌شود که ویجت از بالا و پایین به یک اندازه و همچنین از چپ و راست هم به یک اندازه فاصله گرفته بنابراین در مرکز صفحه قرار می‌گیرد.<br />
با توجه به اینکه در اینجا برای تعیین فاصله از واحدهای اندازه گیری استفاده نشده لذا یک رابط کاربری ساده و انعطاف پذیر داریم که در هر صفحه نمایشی اجرا شود اعم از کوچک و بزرگ یا افقی و عمودی، همواره این TextView در مرکز صفحه قرار خواهد گرفت.<br />
اگر یکی از این خطوط حذف شود چه اتفاقی خواهد افتاد؟ می‌خواهم خط بالا را حذف کنم. برای اینکار هم می‌توان در حالت کد، ویژگی layout_constraintTop_toTopOf را از تکست ویو حذف کرد هم اینکه در حالت دیزاین، خط بالا را انتخاب کرده و delete ‌کنیم. همچنین اگر روی هریک از خطوط راست کلیک کنیم گزینه‌های مختلفی نمایش داده می‌شود که یکی از آنها Delete است:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/delete_constraints.png" alt="حذف خطوط اتصال در ConstraintLayout" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/delete_constraints_2.png" alt="حذف خطوط اتصال در ConstraintLayout" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraint_deleted.png" alt="حذف خطوط اتصال در ConstraintLayout" /><figcaption>حذف خطوط اتصال در ConstraintLayout</figcaption></figure>
<p>مشاهده می‌کنید با حذف خط اتصال بالا، TextView از جهت افقی در مرکز قرار دارد اما در جهت عمودی به پایین صفحه چسبیده است. برای در مرکز قرار گرفتن ویجت کافی است دوباره ویژگی layout_constraintTop_toTopOf با مقدار parent را در ویرایشگر کد اضافه کرده و یا در محیط ویژوال به وسیله عمل Drag &#038; Drop دایره یا گره بالای ویجت را گرفته و روی لبه بالای صفحه رها کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_constraints_to_widgets_in_anroid_studio.png" alt="ساخت خطوط اتصال در ConstraintLayout" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_constraints_to_widgets_2.png" alt="اتصال view ها به یکدیگر در ConstraintLayout" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_constraints_to_widgets_3.png" alt="ایجاد خطوط اتصال در ConstraintLayout" /><figcaption>ایجاد خطوط اتصال در ConstraintLayout</figcaption></figure>
<p>در نهایت مجدد توانستم ویجت را نسبت به بالا و پایین در مرکز قرار دهم.<br />
برای مشاهده جزئیات بیشتر روی گزینه Attributes نوار سمت راست محیط دیزاین کلیک می‌کنم. سپس روی صفحه پیش نمایش یا Component tree ویجت TextView را انتخاب می‌کنم تا ویژگی‌های مرتبط با آن نمایش داده شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/design_attributes_section.png" alt="بخش Attributes در محیط طراحی اندروید استودیو" /><figcaption>بخش Attributes در محیط طراحی اندروید استودیو</figcaption></figure>
<p>در قسمت Declared Attributes عینا مواردی که در کد TextView دیدیم لیست شده و می‌توان هرکدام را تغییر داد.<br />
در قسمت Layout امکانات بیشتری برای اعمال تغییرات وجود دارد. در چهار جهت آیکون مربعی شکل که نماد ویجت انتخاب شده یعنی TextView است، فیلدهای عددی دیده می‌شود که در حالت پیش فرض عدد صفر را نشان می‌دهند. این اعداد، فاصله یا margin را از جهت‌هایی که توسط خط اتصال به والد یا یک ویجت دیگر متصل شده تعیین می‌کنند.<br />
در اینجا که ویجت ما در مرکز صفحه قرار گرفته تعیین فاصله اهمیتی ندارد اما در ادامه آموزش و هنگامی که بخواهیم یک ویجت را در کنار یک ویجت دیگر قرار دهیم از این ویژگی استفاده خواهیم کرد.</p>
<h3 id="bias">تعیین فاصله از چپ و پایین توسط Bias</h3>
<p>در سمت چپ و پایین دو گزینه در کنار عدد مارجین قرار دارد که توسط آن و با حرکت دادن دایره روی خط، میزان فاصله از چهار جهت را می‌توان تغییر داد. این قابلیت Bias نام دارد. دقت داشته باشید در اینجا خبری از واحدهای px و dp نیست بلکه بر اساس درصد تعیین می‌شود. به صورت پیش فرض هردو Bias روی ۵۰ قرار دارد یعنی مرکز صفحه.<br />
اگر بخاطر داشته باشید در کد مربوط به TextView ویژگی با نام Bias وجود نداشت. این یعنی مقدار پیش فرض Bias برای یک ویجت همین ۵۰% یا مقدار ۰٫۵ است و صرفا در صورتی این ویژگی به ویجت اضافه خواهد شد که مقداری غیر از ۵۰ برای آن درنظر بگیریم.<br />
برای تمرین، ویجت روی صفحه یا همین گزینه پایین که مربوط به جهت افقی هست را مقداری به سمت چپ بکشید:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_bias_attribute.png" alt="جابجایی عناصر توسط ویژگی Bias در ConstraintLayout" /><figcaption>جابجایی عناصر توسط ویژگی Bias در ConstraintLayout</figcaption></figure>
<p>ملاحظه می‌کنید عدد Horizontal Bias به ۲۵ تغییر کرد و ویجت هم در جهت افق به اندازه ۲۵ درصد به سمت چپ نزدیکتر شد. حالا اگر کد ویجت را دوباره بررسی کنید خط زیر به آن اضافه شده:</p>
<pre class="brush: xml; title: ; notranslate">
app:layout_constraintHorizontal_bias=&quot;0.25&quot;
</pre>
<p>در محیط ویژوال عدد از ۰ تا ۱۰۰ و در کد از ۰ تا ۱ تعیین می‌شود. یعنی ۲۵ درصد برای Bias افقی (Horizontal) برابر است با مقدار ۰٫۲۵ برای ویژگی آن در کدها.<br />
صفحه پیش نمایش را در حالت افقی (Landscape) قرار می‌دهم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/bias_attribute_in_landscape.png" alt="تست ویژگی Bias در صفحه نمایش Landscape" /><figcaption>تست ویژگی Bias در صفحه نمایش Landscape</figcaption></figure>
<p>باز هم ویجت در فاصله ۲۵ درصدی از سمت چپ صفحه قرار گرفته و انعطاف پذیری ConstraintLayout را ثابت می‌کند.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> ویژگی Bias فقط برای جهتی قابل استفاده است که هردو طرف آن به والد یا ویجت‌های دیگر متصل شده باشد. به طور خلاصه این ویژگی، فاصله ویجت از مرکز در جهت افقی یا عمودی را مشخص می‌کند.
</div>
<h3 id="width-height">تعیین عرض و ارتفاع Widget یا View ها</h3>
<p>عرض و ارتفاع هر ویجتی که به layout اضافه می‌شود ابتدا wrap_content است؛ یعنی به اندازه محتوای درون خودش جا می‌گیرد. اما دو حالت دیگر هم قابل انجام است.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height.png" alt="تغییر عرض و ارتفاع ویو ها در ConstraintLayout" /><figcaption>تغییر عرض و ارتفاع ویو ها در ConstraintLayout</figcaption></figure>
<p>در تصویر بالا مشاهده می‌کنید با بردن نشانگر بر روی یکی از فاصله‌های افقی عبارت Wrap Content نمایش داده می‌شود. یکبار روی یکی از آنها کلیک می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_fixed.png" alt="تعیین حالت Fixed برای اندازه ویجت‌ها" /><figcaption>تعیین حالت Fixed برای اندازه ویجت‌ها</figcaption></figure>
<p>نماد فاصله‌های افقی از دو فلش به یک خط ممتد تغییر پیدا کرد و عبارت Fixed را نمایش می‌دهد.  همچنین در قسمت Declared Attributes مقدار width از wrap_content به ۷۶dp برای من تغییر داده شد یعنی اندروید استودیو یک مقدار پیش فرض در واحد dp را درنظر گرفت. حالا من می‌توانم مقدار مدنظر خودم را بجای ۷۶ وارد کنم.<br />
در مواقعی به این حالت نیاز داریم که بخواهیم برای یک ویجت عرض یا ارتفاع مشخص و ثابتی را درنظر بگیریم و نباید اندازه آن به صفحه نمایش یا حالت قرار گیری آن بستگی داشته باشد.<br />
یک مرتبه دیگر روی نماد فاصله افقی کلیک می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_match_constraints.png" alt="تعیین حالت Match Constraints برای اندازه ویجت‌ها" /></p>
<p>اینبار نوع فاصله گذاری به Match Constraints تغییر پیدا کرد. این حالت همان match_parents در layout هایی مانند RelativeLayout است اما در ConstraintLayout برای اینکه یک ویجت تمام عرض یا ارتفاع در دسترس را اشغال کند بجای match_parent مقدار ۰dp باید داده شود که اندروید استودیو این تغییر را به صورت خودکار برای من انجام داد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_match_constraints_2.png" alt="تعیین حالت Match Constraints برای اندازه ویجت‌ها" /></p>
<p>با انجام این تغییر، ویجت از دو طرف چپ و راست به محلی چسبید که خطوط اتصال برقرار است. در تصویر زیر هنوز هم خطوط اتصال افقی فعال هستند اما چون فاصله‌ای بین TextView و لبه‌های صفحه پیش نمایش وجود ندارد قابل مشاهده نیست. البته از گره‌های تو پر می‌توان فعال بودن این دو را تشخیص داد.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/change_width_&#038;_height_to_match_constraints_3.png" alt="تعیین حالت Match Constraints برای اندازه ویجت‌ها" /><figcaption>تعیین حالت Match Constraints برای اندازه ویجت‌ها</figcaption></figure>
<div class="alert alert-warning">
<span class="notice">نکته:</span> مانند layout های قبل، انجام تغییرات محدود به حالت دیزاین یا کد نیست و به هر دو روش می‌توان کار را انجام داد. اما در مرحله یادگیری توصیه می‌کنم هر تغییری که در یک حالت انجام می‌دهید نتیجه آنرا در حالت دیگر هم بررسی کنید تا ببینید انجام یک کار در محیط ویژوال چه کدی را به کدهای لایه اضافه می‌کند یا بلعکس اگر یک ویژگی به یک ویجت اضافه می‌کنید چه تغییری در صفحه پیش نمایش اعمال می‌شود.<br />
در صورتی که تسلط کافی بر هردو حالت کد و دیزاین داشته باشید در هنگام ساخت و ویرایش صفحات بخصوص layout های پیچیده، کنترل بیشتری بر روی لایه‌ها و ویجت‌ها خواهید داشت.
</div>
<p>خب! حالا که تا حدودی با کلیت کار آشنا شدیم ادامه آموزش را در قالب چند مثال عملی طراحی صفحات با ConstraintLayout پیش می‌برم تا درک بهتری از امکانات آن داشته باشید.<br />
از مسیر New > Layout Resource file یک layout جدید با نام layout_two.xml به پروژه اضافه می‌کنم. هر قسمت را در یک فایل جداگانه توضیح می‌دهم تا همه کدها در سورس پروژه ضمیمه شده را در اختیار داشته باشید.<br />
یک ویجت Button را از پالت ابزار کشیده و در مرکز صفحه پیش نمایش قرار می‌دهم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_view_to_center_of_constraintlayout.png" alt="نمایش یک View در وسط صفحه نمایش در ConstraintLayout" /><figcaption>نمایش یک View در وسط صفحه نمایش در ConstraintLayout</figcaption></figure>
<p>اینجا ظاهرا مشکلی نیست و پس از رها کردن، دکمه در مرکز صفحه باقی می‌ماند. حالا می‌خواهم پروژه را اجرا کنم تا خروجی کار را روی دیوایس اندرویدی هم ببینیم. در MainActivity.java فایل layout_two را جایگزین activity_main می‌کنم تا هنگام اجرای پروژه روی دیوایس نمایش داده شود.</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.constraintlayout;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_two);
    }
}
</pre>
<p>پروژه را روی <a href="http://android-studio.ir/install-genymotion/">شبیه ساز Genymotion</a> اجرا می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_difference_between_preview_&#038;_device.png" alt="تفاوت خروجی ConstraintLayout در صفحه پیش نمایش و دستگاه اندرویدی" /><figcaption>تفاوت خروجی ConstraintLayout در صفحه پیش نمایش و دستگاه اندرویدی</figcaption></figure>
<p>برخلاف آنچه در Preview اندروید استودیو دیدیم که دکمه در مرکز طولی و عرضی صفحه نمایش قرار گرفته بود، روی دیوایس اندرویدی دکمه در گوشه صفحه قرار گرفته است. به سورس layout دقت کنید:</p>
<p><span class="filename">layout_two.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;Button
        android:id=&quot;@+id/button2&quot;
        android:layout_width=&quot;wrap_content&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:text=&quot;Button&quot;
        tools:layout_editor_absoluteX=&quot;156dp&quot;
        tools:layout_editor_absoluteY=&quot;342dp&quot; /&gt;
&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>دو ویژگی layout_editor_absoluteX و layout_editor_absoluteY فقط در صفحه پیش نمایش اندروید استودیو کاربرد دارند و تا زمانی که خطوط constraint برای ویو یا ویجت ساخته نشود موقعیت آن در صفحه تغییری نخواهد کرد. گره‌های چپ و راست دکمه را به دو طرف صفحه متصل می‌کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_left_&#038;_right_constraints.png" alt="اضافه کردن خطوط اتصال چپ و راست در ConstraintLayout" /><figcaption>اضافه کردن خطوط اتصال چپ و راست در ConstraintLayout</figcaption></figure>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/left_&#038;_right_constraints_on_device.png" alt="خروجی Constraints در شبیه ساز اندرویدی" /></p>
<p>مشاهده می‌کنید دکمه در جهت افقی درست در مرکز صفحه قرار گرفته. ویژگی‌های layout_editor_absolute تنها زمانی فعال می‌شود که خطوط اتصال در آن جهت وجود نداشته باشد. بنابراین با اضافه شدن دو خط چپ و راست، ویژگی layout_editor_absoluteX از دکمه حذف شد و فقط layout_editor_absoluteY باقی ماند:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;Button
    android:id=&quot;@+id/button2&quot;
    android:layout_width=&quot;wrap_content&quot;
    android:layout_height=&quot;wrap_content&quot;
    android:text=&quot;Button&quot;
    app:layout_constraintEnd_toEndOf=&quot;parent&quot;
    app:layout_constraintStart_toStartOf=&quot;parent&quot;
    tools:layout_editor_absoluteY=&quot;342dp&quot; /&gt;
</pre>
<p>در حال حاضر نحوه نمایش وضعیت constraint ویجت دکمه به اینصورت است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/no_constraints_in_constraint_widget_tools.png" alt="مدیریت خطوط اتصال در ابزار Constraints Widget" /><figcaption>مدیریت خطوط اتصال در ابزار Constraints Widget</figcaption></figure>
<p>در جهت بالا و پایین نماد + وجود دارد که به وضوح نشان می‌دهد در این گره‌ها اتصالی انجام نشده است. البته اگر اینجا روی + کلیک کنیم علاوه بر ایجاد خط اتصال، یک فاصله (margin) به مقداری که قبلا در layout_editor_absoluteY تعریف شده نیز اضافه خواهد شد.<br />
به عبارت دیگر layout_marginTop جایگزین ویژگی layout_editor_absoluteY می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_button_in_constraint_widget_tool.png" alt="دکمه افزودن خط اتصال در ابزار Constraints Widget" /><figcaption>دکمه افزودن خط اتصال در ابزار Constraints Widget</figcaption></figure>
<p>بنابراین بهتر است ایجاد خط اتصال به همان شیوه Drag &#038; Drop روی صفحه پیش نمایش انجام شود یا در صورت ایجاد آن از طریق دکمه + لازم است مقدار margin صفر و یا به عدد دلخواه اصلاح شود.<br />
در نوار بالای پیش نمایش چندین گزینه وجود دارد که دو مورد را در اینجا و مابقی را بعدا توضیح خواهم داد.</p>
<h3 id="clear-all">حذف یکباره تمامی Constraint ها</h3>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/clear_all_constraints.png" alt="حذف تمامی خطوط اتصال با Clear All Constraints" /><figcaption>حذف تمامی خطوط اتصال با Clear All Constraints</figcaption></figure>
<p>گزینه Clear All Constraints همانطور که از نامش پیداست تمامی اتصالاتی که درون layout بر روی ویجت‌ها اعمال شده را به یکباره حذف می‌کند. این مورد مواقعی کاربرد دارد که تعدادی تغییرات در صفحه انجام داده‌ایم و لازم است همگی اصلاح شوند. با استفاده از این گزینه همه تغییرات قبلی حذف شده و لازم نیست یک به یک constraint ها را حذف کنیم.<br />
همچنین کاربرد دیگر آن هنگامی است که یک layout که قبلا با استفاده از یک ViewGroup دیگر مانند LinearLayout طراحی شده را بخواهیم به ConstraintLayout تبدیل کنیم. </p>
<h3 id="convert-to-constraintlayout">تبدیل ViewGroup های دیگر به ConstraintLayout</h3>
<p>برای مثال قصد داریم رابط کاربری مربوط به <a href="http://android-studio.ir/android-sqlite-database/">آموزش دیتابیس در اندروید</a> که قبلا با استفاده از RelativeLayout ساخته بودیم را به ConstraintLayout تبدیل کنیم. در قسمت Component Tree روی ViewGroup راست کلیک کرده و گزینه Convert RelativeLayout to ConstraintLayout را انتخاب می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/convert_a_viewgroup_to_constraintlayout.png" alt="تبدیل یک ViewGroup به ConstraintLayout در اندروید استودیو" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/convert_a_viewgroup_to_constraintlayout_2.png" alt="تبدیل یک ViewGroup به ConstraintLayout در اندروید استودیو" /><figcaption>تبدیل یک ViewGroup به ConstraintLayout در اندروید استودیو</figcaption></figure>
<p>پس از انجام عملیات تبدیل، اندروید استودیو به صورت خودکار اتصالات Constraint را بر اساس چینش قبلی ویجت‌ها انجام می‌دهد اما خب عمدتا و بخصوص در لایه‌های پیچیده، آن چیزی که ما انتظارش را داریم اتفاق نمی‌افتد و لازم است خودمان نحوه چیدمان المان‌ها را به صورت دستی انجام دهیم.</p>
<h3 id="auto-constraint">ایجاد خودکار Constraint ها</h3>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/infer_constraints.png" alt="ایجاد خودکار خطوط اتصال توسط Infer Constraints" /><figcaption>ایجاد خودکار خطوط اتصال توسط Infer Constraints</figcaption></figure>
<p>گزینه Infer Constraints عملی متضاد با Clear All Constraints را انجام می‌دهد. واژه infer به معنی &#8220;حدس زدن&#8221; است. یعنی در یک layout از جنس ConstraintLayout که یک یا چندین ویجت بدون خطوط اتصال و مابقی قابلیت‌های آن اضافه شده، با کلیک روی این گزینه، اندروید استودیو به صورت حدسی تنظیمات و خطوط اتصال را برای آنها اعمال می‌کند اما همانطور که در قسمت قبل اشاره شد، این تنظیمات عموما رضایت بخش نبوده و در عمل کاربرد زیادی نخواهد داشت.<br />
فعلا با ویجت دکمه کاری ندارم و آنرا از صفحه حذف می‌کنم.<br />
یک گزینه دیگر با نام Default Margins در بالای صفحه Preview وجود دارد:</p>
<h3 id="margin">تعیین فاصله یا Margin پیش فرض</h3>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/default_margins_in_constraintlayout.png" alt="تعیین مقدار Defalut Margin در ConstraintLayout" /><figcaption>تعیین مقدار Defalut Margin در ConstraintLayout</figcaption></figure>
<p>در طراحی یک layout برای ایجاد فاصله بین المان‌های مختلف از ویژگی Margin استفاده می‌کنیم. معمولا فاصله‌ای که در یک layout برای المان‌های مختلف استفاده می‌شود یکسان است تا چینش عناصر در رابط کاربری منظم و یکپارچه باشد.<br />
مقدار Defalut Margin به طور پیش فرض روی عدد ۰ تنظیم شده. من این مقدار را روی ۱۶dp تنظیم می‌کنم و کار را ادامه می‌دهم.<br />
ابتدا یک ImageView به صفحه اضافه می‌کنم: </p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p>در قسمت Attributes عرض این ویجت را ۰dp یا همان Match Constraint و ارتفاع را ۲۰۰dp تعیین می‌کنم. همچنین ویژگی scaleType را بر روی centerCrop تنظیم می‌کنم تا در هر اندازه‌ای از صفحه نمایش، تصویر به طور کامل در محل ImageView قرار گرفته و فضای خالی در اطراف آن ایجاد نشود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout_2.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p>سپس گره‌های سه جهت بالا و چپ و راست را به سه طرف والد یا به عبارتی لبه‌های صفحه نمایش متصل می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout_3.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p>در تصویر بالا ملاحظه می‌کنید دو گره متصل شده یک فاصله ۱۶dp از لبه گرفته‌اند. قبلا این عدد را در Defalut Margin تعریف کرده بودیم. همچنین بعد از اینکه گره‌های بالا و سمت چپ را متصل کردم، به دلیل اینکه قبلا عرض تصویر به اندازه عرض صفحه پیش نمایش کشیده شده بود، گره سمت راست در داخل کادر قرار ندارد. اگر موس را بر روی خط چین سمت راست قرار داده و آنرا به سمت لبه راست صفحه بکشم خط اتصال برقرار می‌شود. یا اینکه در کادر Attributes در قسمت Constraint Widget روی نماد + سمت راست ویجت ImageView کلیک می‌کنم. اینجا فاصله ۱۶dp به طور خودکار اعمال نشد بنابراین به صورت دستی وارد می‌کنم. خروجی نهایی کار به اینصورت خواهد بود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/add_imageview_to_constraintlayout_4.png" alt="افزودن یک ویجت به constraintlayout در اندروید استودیو" /></p>
<p><span class="filename">layout_two.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot; &gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView5&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;200dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginLeft=&quot;16dp&quot;
        android:layout_marginTop=&quot;16dp&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:layout_marginRight=&quot;16dp&quot;
        android:scaleType=&quot;centerCrop&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:srcCompat=&quot;@drawable/img_22&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>تا اینجای کار ما یک تصویر به بالای صفحه اضافه کردیم که در هر اندازه‌ای از صفحه نمایش، از سه جهت فاصله ۱۶dp خواهد داشت.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> دوباره تاکید می‌کنم برای اینکه در طراحی با استفاده از این Layout یا ViewGroup مهارت کافی کسب کنید لازم است تمرینات زیادی انجام دهید. چند صفحه نمونه را به عنوان الگو (از سایر اپلیکیشن‌ها و سورس کدها) جلوی خود قرار داده و سعی کنید صفحه‌ای مشابه آن را بسازید.<br />
ذهنتان را محدود به کارهایی که من انجام می‌دهم نکنید. برای مثال می‌توانستم اندازه کادر تصویر در صفحه پیش نمایش را کوچک کنم تا بتوانم هرسه گره را در داخل صفحه به لبه‌ها متصل کرده و بعد عرض را match constraint تعیین کنم. یا اینکه می‌شد در محیط Code به سادگی ویژگی‌های خط اتصال constraint و margin را برای ImageView تعیین کرد بدون آنکه نیازی به این جابجایی‌ها در محیط Design داشته باشیم.<br />
شاید در نحوه تنظیم چیدمان من ایرادی وجود داشته باشد و شما بتوانید آنرا بهتر و ساده‌تر اجرا کنید. البته شاید که نه؛ حتما!
</div>
<p>در قدم بعد قصد دارم یک آیکون یا تصویر را به نحوی روی تصویر فعلی قرار دهم که مرکز آن، لبه پایینی این ImageView باشد. ابتدا یک ImageView دیگر را از پالت روی صفحه پیش نمایش کشیده و یک آیکون یا تصویر را به دلخواه انتخاب می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget.png" alt="قرار گرفتن مرکز یک ویجت روی حاشیه یک ویجت دیگر" /></p>
<p>کد این ویجت به اینصورت است:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;ImageView
    android:id=&quot;@+id/imageView6&quot;
    android:layout_width=&quot;40dp&quot;
    android:layout_height=&quot;40dp&quot;
    android:background=&quot;#DFCD2E&quot;
    android:padding=&quot;5dp&quot;
    app:srcCompat=&quot;@android:drawable/stat_sys_speakerphone&quot;
    tools:layout_editor_absoluteX=&quot;47dp&quot;
    tools:layout_editor_absoluteY=&quot;244dp&quot; /&gt;
</pre>
<p>حالا گره‌های بالا و پایین آیکون را به گره پایین ImageView قبلی متصل می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_2.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_3.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_4.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p>ملاحظه می‌کنید پس از انجام این کار، مرکز افقی تصویر دوم بر روی خط پایینی تصویر اول قرار گرفت. به همین سادگی!<br />
در نهایت نود سمت چپ را هم به سمت چپ تصویر بزرگ متصل می‌کنم. با توجه به اینکه قبلا مقدار ۱۶dp را برای فاصله پیش فرض یا همان Default Margin تعیین کرده بودم، در اینجا نیز همین مقدار برای فاصله تعیین می‌شود:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_5.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/center_to_bottom_of_widget_6.png" alt="قرار گرفتن مرکز یک ویجت روی حاضیه یک ویجت دیگر" /></p>
<p>حتما کد زیر و سایر کدهای هر قسمت از آموزش را به دقت بررسی کرده تا به خوبی درک کنید با انجام هر کار در محیط دیزاین، چه کدهایی به layout و ویجت‌ها اضافه شده است. </p>
<p><span class="filename">layout_two.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;androidx.constraintlayout.widget.ConstraintLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView5&quot;
        android:layout_width=&quot;0dp&quot;
        android:layout_height=&quot;200dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginLeft=&quot;16dp&quot;
        android:layout_marginTop=&quot;16dp&quot;
        android:layout_marginEnd=&quot;16dp&quot;
        android:layout_marginRight=&quot;16dp&quot;
        android:scaleType=&quot;centerCrop&quot;
        app:layout_constraintEnd_toEndOf=&quot;parent&quot;
        app:layout_constraintStart_toStartOf=&quot;parent&quot;
        app:layout_constraintTop_toTopOf=&quot;parent&quot;
        app:srcCompat=&quot;@drawable/img_22&quot; /&gt;

    &lt;ImageView
        android:id=&quot;@+id/imageView6&quot;
        android:layout_width=&quot;40dp&quot;
        android:layout_height=&quot;40dp&quot;
        android:layout_marginStart=&quot;16dp&quot;
        android:layout_marginLeft=&quot;16dp&quot;
        android:background=&quot;#DFCD2E&quot;
        android:padding=&quot;5dp&quot;
        app:layout_constraintBottom_toBottomOf=&quot;@+id/imageView5&quot;
        app:layout_constraintStart_toStartOf=&quot;@+id/imageView5&quot;
        app:layout_constraintTop_toBottomOf=&quot;@+id/imageView5&quot;
        app:srcCompat=&quot;@android:drawable/stat_sys_speakerphone&quot; /&gt;

&lt;/androidx.constraintlayout.widget.ConstraintLayout&gt;
</pre>
<p>فرض کنید می‌خواهیم یک آیکون دیگر با همین وضعیت در سمت راست تصویر اضافه کنیم. تنها تفاوت در این است که در مرحله آخر بجای گره چپ، باید گره راست آیکون به گره راست تصویر بزرگ متصل شود.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> امیدوارم تا اینجای کار به خوبی درک کرده باشید که اهمیتی ندارد هنگام اضافه کردن یک ویجت از پالت، آنرا کجا رها می‌کنیم. موقعیتی که بعد از رها کردن ویجت روی صفحه پیش نمایش نشان داده می‌شود صرفا مربوط به همین محیط دیزاین اندروید استودیو است.<br />
یعنی اگر آیکونی که در اینجا قبل از اتصال گره‌ها در سمت چپ صفحه قرار داشت را بجای اتصال گره سمت چپ به قسمت چپ تصویر، گره راست آنرا به گره سمت راست تصویر یا لبه سمت راست صفحه متصل می‌کردم، در نهایت در سمت راست قرار می‌گرفت.
</div>
<h3 id="baseline">همتراز کردن ویجت‌های متنی توسط Baseline</h3>
<p>یکی دیگر از قابلیت‌های بسیار کاربردی ConstraintLayout در ویجت‌های متنی مانند EditText و Button ها استفاده می‌شود و Baseline نام دارد. توسط این ویژگی به سادگی می‌توانیم دو ویجت را در راستای افقی در یک تراز قرار دهیم.<br />
یک TextView و یک EditText به صفحه اضافه می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<p>قابلیت Baseline بصورت پیش فرض روی ویجت‌ها فعال نیست و با راست کلیک روی آن و انتخاب گزینه Show Baseline فعال می‌گردد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_2.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<p>پس از انجام این کار یک قسمت کپسولی شکل به وسط EditText اضافه شد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_3.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<p>با کشیدن آن به سمت وسط ویجت TextView کپسول Baseline این ویجت نیز ظاهر می‌شود. بنابراین خطی که از Baseline ویجت EditText کشیده شده را روی آن رها می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_4.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /></p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/constraint/constraintlayout_baseline_5.png" alt="همتراز کردن Widget های متنی توسط ویژگی Baseline" /><figcaption>همتراز کردن Widget های متنی توسط ویژگی Baseline</figcaption></figure>
<p>حالا این دو ویجت در یک تراز قرار گرفته‌اند به طوری که متن درون EditText کاملا همراستای متن TextView دیده می‌شود. با تغییر موقعیت TextView ویجت EditText هم در همان جهت حرکت می‌کند و تراز باز هم برقرار باقی می‌ماند.<br />
منابع تکمیلی: <a href="https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintLayout">Developer</a> ، <a href="https://developer.android.com/reference/androidx/constraintlayout/widget/Group">Developer</a> ، <a href="https://developer.android.com/training/constraint-layout">Developer</a> ، <a href="https://developer.android.com/reference/androidx/constraintlayout/widget/Barrier">Developer</a> ، <a href="https://developer.android.com/reference/androidx/constraintlayout/helper/widget/Flow">Developer</a> ، <a href="https://medium.com/androiddevelopers/introducing-constraint-layout-2-0-9daa3e99995b">Medium</a></p>
<p class="pdfdl">جهت مطالعه ادامه آموزش، فایل PDF را دانلود نمائید</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<p class="copy">با توجه به اینکه آموزش‌های پایه با قیمت پایین در اختیار کاربر قرار گرفته و درآمد حاصل صرف تامین هزینه‌های وب سایت و تهیه آموزش‌های آتی می‌شود، به اشتراک گذاری این فایل با دیگران خلاف اخلاق است.</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه کامل این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۱۰۷<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۴ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : ۴۶ هزار تومان<br />
<span class="notice">توجه: صرفا در صورتی از درگاه پشتیبان استفاده کنید که قادر به پرداخت از طریق سبد دانلود نباشید.</span><br />
<a href="https://android-studio.ir/checkout/?edd_action=add_to_cart&#038;download_id=195541" class="button green edd-submit" rel="noopener noreferrer"><i class="titleicon fa fa-shopping-cart fa-lg"></i>افزودن به سبد دانلود</a> <a href="https://atisdesign.ir/android-files/" class="button red edd-submit" target="_blank" rel="noopener noreferrer">درگاه پشتیبان</a>
</div>
<p>نوشته <a href="https://android-studio.ir/constraintlayout/">طراحی صفحات با ConstraintLayout + سورس پروژه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/constraintlayout/feed/</wfw:commentRss>
			<slash:comments>21</slash:comments>
		
		
			</item>
		<item>
		<title>۲۱ روش افزایش سرعت بیلد Gradle در اندروید استودیو</title>
		<link>https://android-studio.ir/speed-up-android-studio-gradle-build/</link>
					<comments>https://android-studio.ir/speed-up-android-studio-gradle-build/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 09 Feb 2021 10:07:40 +0000</pubDate>
				<category><![CDATA[آموزش‌های تکمیلی]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=195134</guid>

					<description><![CDATA[<p>احتمالا با مشاهده تصویر بالا متوجه شده‌اید که در این مبحث می‌خواهیم راجع به چه موضوعی صحبت کنیم. بسته به میزان حجم یک پروژه اندرویدی، فرایند بیلد شدن آن بین چند ثانیه تا چندین دقیقه می‌تواند زمان بر باشد. بنابراین اجرای روش‌های افزایش سرعت بیلد Gradle در اندروید استودیو می‌تواند تا چندین برابر، مدت زمان [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/speed-up-android-studio-gradle-build/">۲۱ روش افزایش سرعت بیلد Gradle در اندروید استودیو</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/speed_up_android_studio_gradle_build.png" alt="21 روش افزایش سرعت بیلد Gradle در اندروید استودیو" /></p>
<p>احتمالا با مشاهده تصویر بالا متوجه شده‌اید که در این مبحث می‌خواهیم راجع به چه موضوعی صحبت کنیم. بسته به میزان حجم یک پروژه اندرویدی، فرایند بیلد شدن آن بین چند ثانیه تا چندین دقیقه می‌تواند زمان بر باشد. بنابراین اجرای روش‌های افزایش سرعت بیلد Gradle در اندروید استودیو می‌تواند تا چندین برابر، مدت زمان این فرایند طولانی و خسته کننده را کاهش دهد.<br />
بهینه سازی صحیح اندروید استودیو و بیلد سیستم آن یعنی گریدل، می‌تواند یک فرآیند چند دقیقه‌ای بیلد را به چند ثانیه کاهش دهد! پس توصیه می‌کنم این قسمت از سری مباحث <a href="http://android-studio.ir/" target="_blank" rel="noopener">آموزش برنامه نویسی اندروید</a> را با دقت و تا انتها مطالعه کنید.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#reasons">دلایل کاهش سرعت بیلد شدن پروژه اندرویدی</a></li>
<li><a href="#how-to-speed-up-android-studio-gradle-build">روش‌های افزایش سرعت بیلد Gradle در اندروید استودیو</a>
<ul>
<li><a href="#latest-gradle">استفاده از جدیدترین نسخه پلاگین Gradle</a></li>
<li><a href="#latest-java">استفاده از آخرین نسخه Java</a></li>
<li><a href="#offline-gradle">فعال کردن حالت آفلاین Gradle</a></li>
<li><a href="#migrate-to-linux">مهاجرت به لینوکس!</a></li>
<li><a href="#library-dynamic-version">عدم استفاده از ورژن‌های داینامیک کتابخانه‌ها</a></li>
<li><a href="#libraries">استفاده حداقلی از کتابخانه‌ها</a></li>
<li><a href="#apply-changes">اعمال تغییرات بجای اجرای دوباره هنگام تست و دیباگ</a></li>
<li><a href="#improve-gradle">انجام تغییرات در Gradle</a></li>
<li><a href="#disable-png-crunching">غیر فعال کردن PNG Crunching</a></li>
<li><a href="#avoid-legacy-multidex">جلوگیری از Legacy Multidex</a></li>
<li><a href="#avoid-compiling-unnecessary-resources">جلوگیری از کامپایل منابع غیر ضروری</a></li>
<li><a href="#dynamic-variables">عدم استفاده از مقادیر داینامیک</a></li>
<li><a href="#disable-crashlytics">غیر فعال کردن Crashlytics</a></li>
<li><a href="#disable-multi-apk">غیر فعال کردن Multi APK</a></li>
<li><a href="#preDexLibraries">جلوگیری از کامپایل مداوم کتابخانه‌ها</a></li>
<li><a href="#profile-android-project">ساخت پروفایل برای بیلد و بررسی آن</a></li>
<li><a href="#android-modular-project">طراحی ماژولار پروژه</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="reasons">دلایل کاهش سرعت بیلد شدن پروژه اندرویدی</h2>
<p>به نام خدا. عموما وقتی صحبت از کندی سرعت اجرای یک نرم افزار به میان می‌آید در اولین قدم انگشت اتهام به سمت سخت افزار رایانه رفته و پایین بودن کانفیگ سخت افزاری سیستم به عنوان علت اصلی بروز مشکل بیان می‌شود.<br />
قطعا سخت افزار نقش تعیین کننده‌ای در سرعت اجرای ابزار و نرم افزارها به عهده دارد اما این همه‌ی ماجرا نیست. بهینه سازی ابزار مورد استفاده نیز می‌تواند تا حد زیادی و در مواقعی تا چند برابر سرعت انجام عملیات را افزایش دهد بدون آنکه نیازی به ارتقاء سخت افزار و صرف هزینه‌های گزاف وجود داشته باشد.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/gradle_slow_build_speed_reasons.png" alt="دلایل کاهش سرعت بیلد شدن پروژه اندرویدی" /></p>
<p>حتی یک PC یا Laptop قدرتمند و با کانفیگ بالا هم از این قاعده مستثنی نبوده و در صورت عدم بهینه سازی ابزار مورد استفاده، هنگام رندر شدن یک پروژه گرافیکی سنگین یا بیلد شدن پروژه اندرویدی در اندروید استودیو با کاهش بازدهی و افزایش زمان مواجه خواهیم شد.<br />
بنابراین به عنوان یک برنامه نویس و توسعه دهنده اندروید اگر یک سیستم با مشخصات سخت افزاری بالا در اختیار داریم نباید این حس به ما القاء شود که نیازی به بهینه سازی نیست!<br />
در این جلسه سعی می‌کنم مهمترین مواردی که در افزایش سرعت بیلد Gradle در اندروید استودیو موثر هستند را معرفی کنم.</p>
<h2 id="how-to-speed-up-android-studio-gradle-build">روش‌های افزایش سرعت بیلد Gradle در اندروید استودیو</h2>
<p>هر کدام از مواردی که در ادامه ذکر می‌شود درصد متفاوتی در کاهش نهایی زمان بیلد شدن پروژه اندرویدی سهم خواهد داشت بنابراین اکتفا کردن به یک یا چند مورد، تاثیر حداکثری را به همراه نخواهد داشت.<br />
بنابراین درخواست می‌کنم تمامی آیتم‌ها را به دقت و با حوصله کافی مطالعه و اجرا کنید تا بتوانید حداکثر بهره وری را از سیستم خود کسب کرده و در نهایت، کمترین زمان را برای بیلد شدن پروژه‌های اندرویدی خود هدر دهید.</p>
<h3 id="latest-gradle">استفاده از جدیدترین نسخه پلاگین Gradle</h3>
<p>به مرور زمان نسخه‌های جدیدتری از پلاگین گریدل منتشر می‌شود که تعدادی از مشکلات و باگ‌ها رفع شده و نسبت به نسخه‌های قبل بهینه تر هستند که در نتیجه مقداری از زمان انتظار ما برای بیلد شدن پروژه‌های اندرویدی کاسته می‌شود.<br />
البته عمدتا این افزایش بازدهی به قدری ملموس نیست که بخواهد به یکباره زمان بیلد را ۵۰ درصد کاهش دهد اما بهرحال بی تاثیر هم نیست. ضمن اینکه با گذشت زمان و رشد قابلیت‌ها و امکانات ابزارهای هوشمند و بویژه <a href="http://android-studio.ir/introduction-android-operating-system/" target="_blank" rel="noopener">سیستم عامل اندروید</a>، حجم کتابخانه‌ها، پلاگین‌ها و کدهای پروژه اندرویدی نیز افزایش یافته و به نوعی این افزایش سرعت بیلد گریدل و حجم کدها باعث همپوشانی یکدیگر می‌شود.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/gradle_incremental_compile_chart.png" alt="افزایش سرعت کامپایل با بروزرسانی Gradle" /><figcaption>افزایش سرعت بیلد در نسخه‌های جدید Gradle</figcaption></figure>
<p>بر اساس چارت فوق که در <a href="https://gradle.org/whats-new/gradle-5/" target="_blank" rel="noopener">مستندات وب سایت Gradle</a> منتشر شده، در کامپایل یک پروژه جاوایی که حاوی ۱۰۰۰ ماژول بوده، مدت زمان بیلد از حدود ۴ ثانیه در گریدل ۲٫۱۴ به ۲٫۵ ثانیه در گریدل ۵ کاهش یافته است. یعنی چیزی حدود ۳۰ درصد.<br />
برای بروزرسانی Gradle نیاز به انجام کار اضافی نیست. با <a href="http://android-studio.ir/نصب-اندروید-استودیو/" target="_blank" rel="noopener">نصب نسخه جدید اندروید استودیو</a> و یا بروزرسانی آن چنانچه نسخه جدیدی از گریدل و البته سازگار با آن نسخه از اندروید استودیو منتشر شده باشد به صورت خودکار دریافت و جایگزین نسخه قدیمی خواهد شد.<br />
اما چنانچه قصد دارید بدون بروزرسانی اندروید استودیو از آخرین نسخه گریدل استفاده کنید لازم است نسخه پلاگین در build.gradle (Project) در بلاک dependencies بروز شود:</p>
<pre class="brush: java; title: ; notranslate">
dependencies {
    classpath &quot;com.android.tools.build:gradle:4.1.1&quot;
}
</pre>
<p>در زمان تهیه این آموزش Gradle plugin 4.1.1 روی اندروید استودیو من فعال است که البته به من پیشنهاد می‌دهد نسخه ۴٫۱٫۲ را نصب کنم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/android_studio_gradle_plugin_update.png" alt="بروزرسانی پلاگین گریدل در اندروید استودیو" /><figcaption>بروزرسانی پلاگین گریدل در اندروید استودیو</figcaption></figure>
<p>پلاگین را به ۴٫۱٫۲ ارتقاء داده و پروژه را Sync می‌کنم. بلافاصله اندروید استودیو شروع به دانلود گریدل ۶٫۵ می‌کند:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/android_studio_gradle_update.png" alt="بروزرسانی گریدل در اندروید استودیو" /><figcaption>بروزرسانی گریدل در اندروید استودیو</figcaption></figure>
<p>یعنی برای استفاده از نسخه جدید پلاگین گریدل، خود گریدل نیز باید ابتدا بروز شود. لینک فایل گریدل در gradle-wrapper.properties ذخیره می‌شود که در صورت نیاز می‌توان به صورت دستی هم آنرا تغییر داد:</p>
<p><span class="filename">gradle-wrapper.properties</span></p>
<pre class="brush: java; title: ; notranslate">
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
</pre>
<div class="alert alert-warning">
<span class="notice">تذکر:</span> اگر در حال مطالعه این آموزش هستید یعنی تجربه کار با اندروید استودیو و مشکلات مربوط به تحریم را دارید. چنانچه از ابزاری مانند FOD برای دور زدن تحریم و <a href="http://android-studio.ir/how-to-bypass-software-sanctions/" target="_blank" rel="noopener">فعال کردن پروکسی روی اندروید استودیو</a> استفاده می‌کنید به دلیل اینکه این سرویس‌ها صرفا آدرس‌های تحریم شده را از پروکسی خود عبور می‌دهند و در حال حاضر سرویس Gradle بر روی IP ایران تحریم نیست، لازم است قبل از شروع دانلود، پروکسی را غیر فعال کنید.<br />
البته شاید بعد از دریافت و نصب گریدل، اندروید استودیو بخواهد فایل‌های دیگری دریافت کند که مشمول تحریم بوده و لازم باشد مجدد پروکسی را فعال نمایید!
</div>
<p>برای کسب اطلاع دقیق از جزئیات مربوط به نسخه‌های گریدل و پلاگین‌های سازگار با آن به صفحه <a href="https://developer.android.com/studio/releases/gradle-plugin" target="_blank" rel="noopener">Android Gradle plugin</a> مراجعه کنید.</p>
<h3 id="latest-java">استفاده از آخرین نسخه Java</h3>
<p>در زمان تهیه این آموزش Java 8 آخرین نسخه از جاوا است. جاوا ۸ (با عنوان ۱٫۸) نسبت به نسخه‌های قبلی خود سرعت بیشتری دارد و اگر هنوز از جاوا ۱٫۶ یا ۱٫۷ استفاده می‌کنید حتما آن را به ۱٫۸ یا جدیدترین نسخه موجود ارتقا دهید.<br />
البته اگر به یاد داشته باشید در نسخه‌های ابتدایی محیط توسعه اندروید استودیو لازم بود JDK را به صورت دستی روی سیستم نصب کرده و سپس محل نصب را به آن معرفی کنیم. اما در نسخه‌های اخیر، تیم توسعه دهنده اندروید استودیو این زحمت را هم از روی دوش برنامه نویس‌های اندرویدی برداشته و یک نسخه از OpenJDK را درون IDE تعبیه کرده‌اند.<br />
بنابراین با بروزرسانی اندروید استودیو مطمئن هستیم که آخرین نسخه از JDK را استفاده می‌کنیم. با اینحال برای اطمینان از نصب آخرین نسخه جاوا کافیست بلاک compileOptions در build.gradle (Module:app) را بررسی کنیم:</p>
<pre class="brush: java; title: ; notranslate">
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
</pre>
<p>در کد فوق نسخه ۸ جاوا قید شده. همچنین در محل نصب JDK توسط Command line می‌توان با دستور java -version نسخه جاوای موجود را بررسی کرد.<br />
اگر اندروید استودیو را در مسیر پیش فرض نصب کرده‌اید محلی که می‌توان نسخه جاوا را چک کرد به صورت زیر است:</p>
<p style="text-align: left; direction: ltr;">
C:\Program Files\Android\Android Studio\jre\bin
</p>
<p>در سیستم عامل ویندوز در این پنجره روی صفحه shift و راست کلیک کرده و گزینه Open PowerShell window here یا Open Command line window here را انتخاب می‌کنم تا پنجره خط فرمان باز شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/use_latest_jdk_in_android_studio.png" alt="بررسی نسخه جاوای نصب شده روی سیستم" /><figcaption>بررسی نسخه جاوای نصب شده روی سیستم</figcaption></figure>
<p>ملاحظه می‌کنید با نوشتن دستور java -version نسخه ۱٫۸ نمایش داده شد.</p>
<h3 id="offline-gradle">فعال کردن حالت آفلاین Gradle</h3>
<p>یکی دیگر از گزینه‌های افزایش سرعت بیلد Gradle در اندروید استودیو آفلاین کردن گریدل است. چنانچه گریدل در حالت پیش فرض خود یعنی Online قرار داشته باشد ممکن است در حین فرایند بیلد پروژه دانلود تعدادی فایل را آغاز کند که همین امر موجب افزایش زمان بیلد می‌شود. بهتر است فقط زمانی گریدل را در حالت آنلاین قرار دهیم که می‌خواهیم یک کتابخانه جدید به پروژه اضافه کنیم که قبلا استفاده نشده و در کش گریدل موجود نیست.<br />
کتابخانه‌هایی که قبلا یکبار در همین پروژه یا پروژه‌ای دیگر استفاده شده و به صورت آنلاین دریافت شده‌اند تا زمانی که به صورت دستی حذف نشود در کش موجود بوده و بدون نیاز به اتصال مجدد قابل استفاده خواهد بود (مگر آنکه بخواهیم نسخه جدیدتر آن کتابخانه را نصب کنیم).<br />
برای اینکار کافیست در نوار سمت راست اندروید استودیو و یا منوی View > Tool windows گزینه Gradle و سپس Toggle Offline Mode را انتخاب کنید:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/enable_android_studio_gradle_offline_mode.png" alt="فعال کردن حالت آفلاین Gradle در اندروید استودیو" /><figcaption>فعال کردن حالت آفلاین Gradle در اندروید استودیو</figcaption></figure>
<h3 id="migrate-to-linux">مهاجرت به لینوکس!</h3>
<p>قبلا هم می‌دانستم اندروید استودیو در اجرا و همچنین بیلد شدن پروژه‌ها در توزیع‌های لینوکسی محبوب مانند Ubuntu سرعت بیشتری نسبت به ویندوز دارد. اما برای اطمینان بیشتر عبارت <span dir="ltr">&#8220;Does Android Studio run faster on Linux?&#8221;</span> را گوگل کردم تا تجربه سایر افراد را در این زمینه بدانم. تقریبا همه آنها از افزایش سرعت حتی تا %۵۰ بعد از مهاجرت به Ubuntu را اعلام کرده بودند. برای نمونه می‌توانید <a href="https://www.quora.com/Does-Android-Studio-run-faster-on-Ubuntu-or-Windows" target="_blank" rel="noopener">این بحث موجود در Quara</a> را بررسی کنید.<br />
البته برای کسی مثل من شاید مهاجرت از ویندوز به لینوکس سخت باشد اما بهرحال یکی از گزینه‌ها بود و لازم میدانستم به آن اشاره کنم.</p>
<h3 id="library-dynamic-version">عدم استفاده از ورژن‌های داینامیک کتابخانه‌ها</h3>
<p>اگر عادت کرده‌اید هنگام افزودن یک کتابخانه به <a href="http://android-studio.ir/create-android-project-and-its-structure/" target="_blank" rel="noopener">پروژه اندرویدی</a> خود برای سادگی کار بجای درج نسخه دقیق آن، یک &#8220;+&#8221; قرار دهید از امروز اشتباه خود را اصلاح کنید! داینامیک بودن نسخه کتابخانه‌ها باعث می‌شود تا گریدل هر ۲۴ ساعت یکبار به مخزن آنلاین متصل شده تا بررسی کند آیا نسخه جدیدتری منتشر شده یا نه.<br />
برای مثال در زمان نگارش این آموزش، ورژن ۲٫۹٫۰ آخرین نسخه از <a href="http://android-studio.ir/android-retrofit-library/" target="_blank" rel="noopener">کتابخانه Retrofit</a> است.</p>
<pre class="brush: java; title: ; notranslate">
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:+'
}
</pre>
<pre class="brush: java; title: ; notranslate">
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}
</pre>
<p>در کدهای فوق، مورد اول اشتباه و مورد دوم صحیح است. بگذریم از اینکه در مواردی حتی کتابخانه‌های پیش فرض پروژه هم به همین صورت تعریف شده است:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/avoid_dependency_dynamic_version.png" alt="استفاده از ورژن‌های استاتیک برای کتابخانه‌ها" /><figcaption>استفاده از ورژن‌های استاتیک برای کتابخانه‌ها</figcaption></figure>
<h3 id="libraries">‌استفاده حداقلی از کتابخانه‌ها</h3>
<p>بالا رفتن تعداد کتابخانه‌های اضافه شده به پروژه می‌تواند سرعت بیلد را به همان نسبت کاهش دهد. اگر کتابخانه‌ای را قبلا اضافه کرده‌اید و الان به هر دلیل از استفاده از آن منصرف شده‌اید حتما نسبت به حذف آن از پروژه اقدام کنید.<br />
همچنین برخی کتابخانه‌ها ترکیبی از دو یا چند کتابخانه دیگر هستند. اگر فقط به یکی از زیر مجموعه‌های آن نیاز دارید فقط همان را اضافه کنید تا از اضافه شدن کتابخانه‌های بلا استفاده جلوگیری گردد.<br />
ضمن اینکه توجه داشته باشید یک کتابخانه فقط یکبار به پروژه اضافه شده باشد. برای مثال کتابخانه Retrofit Gson Converter خودش از کتابخانه Gson گوگل استفاده می‌کند بنابراین نیازی نیست در کنار آن، دوباره کتابخانه Gson را تعریف کنیم.<br />
با استفاده از دستور</p>
<pre class="brush: java; title: ; notranslate">
gradlew app:dependencies
</pre>
<p>در Terminal می‌توانید لیست کامل کتابخانه‌های بکار رفته در پروژه را مشاهده کنید:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/list_of_android_project_dependencies_&#038;_libraries.png" alt="‌استفاده حداقلی از کتابخانه‌ها در پروژه اندرویدی" /><figcaption>لیست کتابخانه‌های پروژه در Terminal</figcaption></figure>
<p>نحوه کار با ترمینال در ادامه همین آموزش (ساخت پروفایل برای بیلد و بررسی آن) توضیح داده شده است.</p>
<h3 id="apply-changes">اعمال تغییرات بجای اجرای دوباره هنگام تست و دیباگ</h3>
<p>اگر پروژه روی <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener">شبیه ساز اندرویدی</a> یا یک دیوایس حقیقی در حال اجرا (Run) است و می‌خواهیم تغییری در کدها اعمال کنیم، بهتر است بجای Run کردن دوباره‌ی پروژه از گزینه Apply Changes استفاده کنیم. با اجرای مجدد پروژه، اپلیکیشن به طور کامل ریستارت شده و به عبارتی یک APK جدید ساخته می‌شود که جایگزین APK ای که در اجرای قبلی روی دیوایس نصب شده خواهد شد. این یعنی اتلاف زمان و منابع.<br />
در صورتی که گزینه Apply Changes صرفا بخشی از برنامه که کدهای آن ویرایش شده را به دیوایسی که پروژه روی آن در حال اجراست ارسال می‌کند و یک APK جدید جایگزین نخواهد شد.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/using_apply_changes_instead_of_run.png" alt="استفاده از Apply Changes بجای Run" /><figcaption>استفاده از Apply Changes بجای Run</figcaption></figure>
<p>قابلیت Apply Changes در اندروید استودیو ۳٫۵ معرفی و جایگزین Instant Run شد که در نسخه ۲ اضافه شده بود. بر اساس مستندات توسعه دهندگان اندروید استودیو، برخلاف Instant Run که تغییرات انجام شده در پروژه را از طریق بازنویسی بایت کد APK روی دیوایس اعمال می‌کرد، در Apply Changes کلاس‌های موردنظر دوباره تعریف می‌شوند که فرایند بهینه‌تر و کوتاهتری حاصل می‌شود.<br />
البته استفاده از این قابلیت صرفا بر روی دیوایس‌های با Android 8 (API 26) و به بالا قابل انجام است. ضمن اینکه اعمال برخی تغییرات در پروژه‌در در حال اجرا صرفا با ریستارت شدن یا به عبارتی Run کردن دوباره پروژه انجام خواهد شد و Apply Changes کاربردی نخواهد داشت مانند حذف یا اضافه کردن یک متد، فیلد یا کلاس.<br />
توضیحات تکمیلی را در صفحه <a href="https://developer.android.com/studio/run#apply-changes-limitations" target="_blank" rel="noopener">Build and Run</a> مستندات اندروید مطالعه کنید.</p>
<h3 id="improve-gradle">انجام تغییرات در Gradle</h3>
<p>با فعال کردن تعدادی از قابلیت‌های گریدل می‌توان باز هم افزایش سرعت بیلد Gradle در اندروید استودیو را شاهد باشیم. این کدها در فایل gradle.properties اضافه می‌شود:</p>
<p><b>Gradle Daemon:</b> این قابلیت یک فرایند پس زمینه‌ای است و فعال کردن آن باعث می‌شود برای عملیات بیلد پروژه، گریدل حافظه بیشتری در اختیار داشته باشد که به کاهش زمان انجام عملیات منجر خواهد شد.</p>
<pre class="brush: java; title: ; notranslate">
org.gradle.deamon=true
 </pre>
<p>اگر مایلید در این زمینه اطلاعات کاملتری کسب کنید صفحه <a href="https://docs.gradle.org/current/userguide/gradle_daemon.html" target="_blank" rel="noopener">Gradle Daemon</a> را مطالعه کنید.</p>
<p><b>Parallel Build Execution:</b> به طور پیش فرض گریدل تنها یک کار را در آن واحد انجام می‌دهد اما با اضافه کردن خط زیر، گریدل چندین عملیات را همزمان و به طور موازی اجرا خواهد کرد.</p>
<pre class="brush: java; title: ; notranslate">
org.gradle.parallel=true
</pre>
<p>البته این قابلیت تنها هنگامی موثر است که پروژه ماژولار ساخته شده باشد. آشنایی بیشتر با این قابلیت در <a href="https://docs.gradle.org/nightly/userguide/performance.html#parallel_execution" target="_blank" rel="noopener">این صفحه</a>.</p>
<p><b>Configure On Demand:</b> احتمال اینکه این قابلیت مورد استفاده ما قرار گیرد بسیار کم است زیرا صرفا در پروژه‌ای که اصطلاحا Multi-project نامیده می‌شود کاربرد خواهد داشت. پروژه‌ای که خودش دارای چند زیرشاخه مانند mobile و tv باشد.<br />
به بیان ساده تر، هنگامی که بخواهیم این Multi-project را روی یک دستگاه موبایل اجرا کنیم، فقط همین قسمت بیلد شده و قسمت مربوط به tv بیلد نخواهد شد.</p>
<pre class="brush: java; title: ; notranslate">
org.gradle.configureondemand=true
</pre>
<p>اطلاعات بیشتر در این زمینه: <a href="https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand" target="_blank" rel="noopener">اینجا</a></p>
<p><b>تخصیص حافظه بیشتر به کامپایلر جاوا:</b> می‌توانیم مقدار حافظه در دسترس برای کامپایلر را افزایش دهیم تا فرایند کامپایل با سرعت بالاتری انجام شود. برای مثال در خط زیر مقدار ۲ گیگابایت (۲۰۴۸ مگابایت) تعیین شده است. </p>
<pre class="brush: java; title: ; notranslate">
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
</pre>
<p>بر اساس مقدار حافظه سیستم می‌توان عدد بزرگتری نیز در نظر گرفت.</p>
<p><b>فعال کردن Gradle build cache:</b> با فعال شدن قابلیت کش، گریدل از خروجی بیلدهای قبلی پروژه استفاده می‌کند که می‌تواند تا حدود ۳ برابر برای یک بیلد کامل (Full build) و ۱۰ برابر برای یک اعمال تغییر کوچک در پروژه (Incremental build) افزایش سرعت و کاهش زمان بیلد را به همراه داشته باشد.</p>
<pre class="brush: java; title: ; notranslate">
org.gradle.caching=true
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> علاوه بر کش بیلد گریدل، خود اندروید نیز یک سیستم کش دارد که تا قبل از انتشار اندروید استودیو ۲٫۳ توسط خط android.enableBuildCache=true فعال می‌شد. اما از این نسخه به بعد این قابلیت به صورت پیش فرض فعال بوده و نیازی به اضافه کردن آن نیست.
</div>
<h3 id="disable-png-crunching">غیر فعال کردن PNG Crunching</h3>
<p>هنگام بیلد شدن پروژه برای کاهش حجم فایل اپلیکیشن چنانچه تصاویری با فرمت PNG در منابع وجود داشته باشد فشرده خواهند شد که این فرایند به مدت زمان بیلد می‌افزاید. مسلما این قابلیت برای نسخه release نهایی که قصد انتشار آن را داریم یک مزیت محسوب می‌شود اما در هنگام تست و عیب یابی پروژه طبیعتا کاهش حجم اپ اهمیتی برای ما نخواهد داشت. لذا با غیر فعال کردن آن تا حدودی از زمان بیلد کاسته خواهد شد.<br />
البته از اندروید استودیو ۳ به بعد این قابلیت به طور پیش فرض برای خروجی debug غیر فعال بوده و صرفا برای نسخه release انجام این کار لازم است. در بلاک release داخل بلاک BuildTypes فایل build.gradle(app) برای ویژگی crunchPngs مقدار false تعریف می‌کنم. اگر به یاد داشته باشید تنظیمات مربوط به <a href="http://android-studio.ir/android-app-source-protect-and-optimize-using-proguard-r8/" target="_blank" rel="noopener">فعال کردن پروگارد (R8)</a> نیز در همین بلاک قرار داشت.</p>
<pre class="brush: java; title: ; notranslate">
buildTypes {

    release {

        crunchPngs false

    }
}
</pre>
<p>البته شاید بهتر باشد بجای استفاده از تصاویر PNG از همان ابتدا از فرمت جدید تصاویر با نام <a href="https://developers.google.com/speed/webp/" target="_blank" rel="noopener">WebP</a> استفاده کنیم تا اولا نیازی به غیر فعال کردن موقت crunchPngs و فعال کردن مجدد آن هنگام release نهایی نداشته باشیم و دوما در همه حالت‌ها چه debug و چه release تست و نهایی، حجم برنامه ما در کمترین حالت ممکن از نظر resource ها قرار بگیرد.<br />
WebP فرمتی است که عمدتا در وب در حال فراگیر شدن بوده و جایگزینی آن با فرمت PNG به مرور در حال افزایش است. اگر با واژه سئو (SEO) آشنا هستید، یکی از آیتم‌های مهم در بهبود وضعیت سئو یک وب سایت، کاهش حجم صفحات و بخصوص تصاویر درون آن به شمار می‌رود.<br />
با اینحال چنانچه به هر دلیلی استفاده از WebP برایتان مقدور نیست، غیر فعال کردن crunchPngs به عنوان راهکار جایگزین در دسترس قرار دارد.</p>
<h3 id="avoid-legacy-multidex">جلوگیری از Legacy Multidex</h3>
<p>قبل از پرداختن به این مورد خلاصه بگویم؛ اگر برای تست و <a href="http://android-studio.ir/run-debug-android-app-on-hardware-device/" target="_blank" rel="noopener">دیباگ پروژه اندرویدی</a> خود صرفا از دیوایس‌های مجازی یا حقیقی با API 21 (Android 5.0) و به بالا استفاده می‌کنید مشکلی وجود ندارد و بدون مطالعه این قسمت از آن عبور کنید.<br />
قبلا در مبحث آشنایی با سیستم عامل اندروید گفتیم که از نسخه ۵٫۰ اندروید، ران تایم ART جایگزین Dalvik شد. محدودیتی که در Dalvik وجود دارد این است که در یک فایل DEX (که بعد از کامپایل پروژه درون APK ساخته می‌شود) حداکثر ۶۴k یا به عبارتی ۶۵۵۳۶ متد می‌تواند ارجاع داده شود.<br />
لذا چنانچه در حال توسعه یک پروژه با مقیاس بزرگ هستیم و مجموع تعداد متدهای تعریف شده در پروژه (شامل کدها، کتابخانه‌ها و&#8230;) بیشتر از این عدد باشد و minSdkVersion هم روی API 20 و یا پایینتر تنظیم شده باشد، لازم است کتابخانه Multidex روی پروژه نصب و فعال شود تا متدها در دو یا چند فایل DEX قرار گیرد.<br />
این آموزش جایی برای پرداختن به نحوه فعالسازی این کتابخانه نیست و چنانچه یکی از ارورهای </p>
<pre class="brush: java; title: ; notranslate">
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
</pre>
<p>و یا </p>
<pre class="brush: java; title: ; notranslate">
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in &#x5B;0, 0xffff]: 65536
</pre>
<p>گرفته‌اید صفحه <a href="http://developer.android.com/studio/build/multidex" target="_blank" rel="noopener">فعالسازی Multidex</a> را در مستندات اندروید مطالعه کنید.<br />
بهتر است قبل از فعالسازی Multidex یکبار پروژه را بازنگری کنید. اگر کد یا کتابخانه اضافی هست که امکان حذف آن وجود دارد و با اینکار، تعداد متدها از محدودیت اعلام شده کمتر خواهد شد، نسبت به استفاده از Multidex در اولویت خواهد بود.<br />
فعال بودن این قابلیت نه تنها باعث افزایش سرعت بیلد Gradle در اندروید استودیو نمی‌شود بلکه با کاهش سرعت و افزایش زمان بیلد مواجه خواهیم شد. لذا یا باید minSdkVersion را روی API 21 و به بالا تنظیم کرد و یا اینکه پروژه را روی دیوایس‌های Android 5.0 به بالا اجرا و تست کنیم تا Multidex دخالتی در فرایند بیلد شدن پروژه نداشته باشد. ضمن اینکه نباید از اندروید استودیو نسخه ۲٫۳ و پایینتر از آن استفاده کرد.</p>
<h3 id="avoid-compiling-unnecessary-resources">جلوگیری از کامپایل منابع غیر ضروری</h3>
<p>اگر برنامه‌ای که در حال توسعه آن هستید شامل چند زبان مانند فارسی و انگلیسی بوده و یا برای چندین اندازه صفحه طراحی شده، هنگام بیلد شدن پروژه تمامی این resource ها کامپایل می‌شود. در صورتی که هنگام تست و دیباگ ما فقط به یک زبان و یک اندازه تراکم صفحه نیاز داریم.<br />
بنابراین با محدود کردن این موارد در هنگام تست و دیباگ پروژه می‌توانیم از اتلاف زمان برای کامپایل شدن منابع اضافی و غیر ضرور اجتناب کنیم.<br />
یک بلاک با نام productFlavors در بلاک android در build.gradle(app) اضافه می‌کنم. تنظیماتی که مدنظر دارم فقط باید در هنگام تست پروژه اجرا شود و نه در زمان <a href="http://android-studio.ir/generating-signed-apk/" target="_blank" rel="noopener">گرفتن خروجی APK یا AAB</a> برای انتشار. بنابراین یک بلاک جدید با نام dev در productFlavors تعریف کرده و تنظیمات موردنظرم را داخل آن اضافه می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
android {

    ...

    productFlavors {

        dev {

            resConfigs &quot;fa&quot;, &quot;xhdpi&quot;

        }

    }

}
</pre>
<p>در مثال فوق هنگام Run شدن پروژه و یا گرفتن نسخه debug فقط زبان فارسی و اندازه صفحه (تراکم صفحه) xhdpi کامپایل شده و مابقی موارد چشم پوشی خواهد شد.</p>
<h3 id="dynamic-variables">عدم استفاده از مقادیر داینامیک</h3>
<p>اگر در مواردی مانند تعیین versionCode در پروژه اندرویدی خود از مقادیر داینامیک بجای استاتیک استفاده می‌کنید، در عین حال اینکه از انجام یک کار روتین راحت می‌شوید اما باید بدانید که به مدت زمان بیلد پروژه افزوده خواهد شد.</p>
<pre class="brush: java; title: ; notranslate">
def buildTime = new Date().format('yyMMddHHmm').toInteger()

android {
    
    defaultConfig {
        
        versionCode buildTime

    }

}
</pre>
<p>در کد فوق از کلاس Date جاوا برای تعیین versionCode استفاده شده. بهتر است از انجام این کار اجتناب کرده و کد را به صورت دستی تغییر دهید.</p>
<h3 id="disable-crashlytics">غیر فعال کردن Crashlytics</h3>
<p>اگر برای دریافت گزارشات کرش برنامه خود از ابزار Crashlytics استفاده می‌کنید اما نیازی نمی‌بینید هنگام دیباگ فعال باشد بهتر است این پلاگین در نسخه‌های debug غیر فعال شود تا از تاثیر منفی آن در مدت زمان بیلد شدن پروژه جلوگیری گردد.<br />
برای اینکار خط زیر را در بلاک debug می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
android {
    ...

    buildTypes {

        debug {

            ext.enableCrashlytics = false

        }

    }

}
</pre>
<p>اما اگر نیاز داریم این پلاگین در هنگام دیباگ هم فعال باشد باز هم با جلوگیری از بروز شدن Build id پلاگین Crashlytics در هر بیلد، می‌توان تا حدودی از زمان بیلد را کاهش داد:</p>
<pre class="brush: java; title: ; notranslate">
android {
    ...

    buildTypes {

        debug {

            ext. alwaysUpdateBuildId = false

        }

    }

}
</pre>
<h3 id="disable-multi-apk">غیر فعال کردن Multi APK</h3>
<p>اگر از قابلیت Multi APK برای ساخت چندین نسخه از اپلیکیشن برای دیوایس‌های با مشخصات سخت افزاری متفاوت استفاده می‌کنید، قطعا در هنگام تست و دیباگ نیازی به این قابلیت نیست و بهتر است غیر فعال شود.<br />
کافیست در بلاک debug دو خط زیر تعریف شود تا برای همه سایزهای صفحه نمایش (density) و همچنین معماری‌های CPU (abi) فقط یک APK ساخته شود:</p>
<pre class="brush: java; title: ; notranslate">
buildTypes {
    ...
    debug {
        splits.abi.enable = false
        splits.density.enable = false
    } 
}
</pre>
<h3 id="preDexLibraries">جلوگیری از کامپایل مداوم کتابخانه‌ها</h3>
<p>طبیعتا در حین توسعه یک اپلیکیشن بارها و بارها نیاز به اصلاح و تغییر کدها و تست مجدد آن روی دیوایس اندرویدی داریم. اما کتابخانه‌هایی که در پروژه استفاده شده بدون تغییر هستند و واقعا نیازی نیست با هر بار بیلد شدن پروژه، کتابخانه‌های داخل پروژه نیز دوباره بیلد شده و درون فایل DEX در کنار دیگر کدهای کامپایل شده قرار گیرد.<br />
با افزودن بلاک dexOptions به بلاک android در build.gradle(app) و تعیین مقدار true برای ویژگی preDexLibraries از این پس کتابخانه‌ها فقط یکبار کامپایل شده و در دفعات بعد فقط کدهای پروژه مجدد کامپایل خواهند شد.</p>
<pre class="brush: java; title: ; notranslate">
android {

  ...    

    dexOptions { 

        preDexLibraries true

    }

}
</pre>
<p>البته دقت داشته باشید این قابلیت فقط در تغییرات جزئی (Apply Changes) موجب افزایش سرعت بیلد Gradle در اندروید استودیو خواهد شد ولی در بیلدهای کامل می‌تواند باعث کاهش سرعت بیلد گریدل شود. بنابراین بهتر است هنگام بیلدهای کامل، این قابلیت غیر فعال (false) باشد.</p>
<h3 id="profile-android-project">ساخت پروفایل برای بیلد و بررسی آن</h3>
<p>یکی از امکانات دیگری که Gradle در اختیار توسعه دهندگان قرار داده، ساخت Profile برای هر بیلد است. با فعال کردن پروفایل، بعد از هر خروجی APK ای که از پروژه می‌گیریم، یک گزارش جامع و کامل پیرامون مراحل بیلد و اینکه هر کدام چه مدت زمانی از فرایند کامل بیلد را به خود اختصاص داده در قالب یک فایل HTML در مسیر Project Directory > build > reports > profile ذخیره می‌شود.<br />
برای فعال کردن profile دو راه وجود دارد:<br />
<b>۱٫ تعریف دستور <span dir="ltr">&#8211; -profile</span> در تنظیمات کامپایلر اندروید استودیو:</b> کافیست یکبار دستور <span dir="ltr"><code>--profile</code></span> را در قسمت Command-line Options در مسیر Settings > Build, Execution, Deployment > Compiler تعریف کنیم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/add_--profile_command_to_command-line_options.png" alt="ساخت پروفایل برای بیلد و بررسی آن" /><figcaption>اضافه کردن دستور <span dir="ltr">&#8211;profile</span> به تنظیمات کامپایلر اندروید استودیو</figcaption></figure>
<p>حالا با هربار اجرای پروژه روی شبیه ساز یا دیوایس واقعی و یا گرفتن APK نسخه debug یا release یک پروفایل با پسوند html در پوشه profile ذخیره خواهد شد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/android_studio_gradle_build_profile_reports.png" alt="لیست گزارش‌های پروفایل بیلد گریدل" /><figcaption>لیست گزارش‌های پروفایل بیلد گریدل</figcaption></figure>
<p>با باز کردن هر فایل در مرورگر، تمامی جزئیات بیلد در ۵ دسته بندی متفاوت نمایش داده می‌شود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/android_studio_gradle_build_profile_report.png" alt="پروفایل گزارش بیلد گریدل" /><figcaption>پروفایل گزارش بیلد گریدل</figcaption></figure>
<p>در هر قسمت، عدد زیر Duration زمانِ مربوط به یک بخش از فرایند بیلد را نشان می‌دهد. با بررسی همه قسمت‌ها شامل Summary، Configuration، Dependency Resolution، Artifact Transforms و Task Execution می‌توانیم تشخیص دهیم چه مواردی بیشترین زمان بیلد را به خود اختصاص می‌دهد و کدامیک را می‌توان بهینه و یا کاملا حذف کرد.<br />
<b>۲٫ اضافه کردن <span dir="ltr">&#8211; -profile</span> به دستورات در خط فرمان:</b> اگر عادت به استفاده از دستورات دارید، برای اضافه کردن پروفایل به بیلد باید <span dir="ltr"><code>--profile</code></span> به انتهای دستور اضافه شود:</p>
<pre class="brush: java; title: ; notranslate">
gradlew assembleDebug --profile
</pre>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/speed/add_--profile_to_gradlew_command.png" alt="اضافه کردن profile به دستور دیباگ در ترمینال" /><figcaption>اضافه کردن profile به دستور دیباگ در ترمینال</figcaption></figure>
<p>ملاحظه می‌کنید پس از اجرای فرمان در Terminal اندروید استودیو، بیلد پروژه انجام شده و در انتهای آن لینک فایل html پروفایل هم نمایش داده شده است.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> اگر تمایل دارید با ترمینال آشنا شوید و گزینه Terminal در نوار پایین اندروید استودیو فعال نبود، در منوی View > Tool Windows آنرا پیدا کنید. همچنین اگر بجای ترمینال اندروید استودیو از Command-line ویندوز استفاده می‌کنید، پس از بار کردن آن در پوشه پروژه، دستور را به این صورت وارد کنید:</p>
<pre class="brush: java; title: ; notranslate">
./gradlew assembleDebug --profile
</pre>
</div>
<h3 id="android-modular-project">طراحی ماژولار پروژه</h3>
<p>Modular که در فارسی ماژولار نامیده می‌شود بیانگر سبکی از طراحی و ساخت است که یک پروژه بزرگ از ترکیب چندین واحد کوچکتر تشکیل می‌شود که هر واحد را یک ماژول می‌نامیم.<br />
تقسیم بندی برنامه به چند واحد (ماژول) علاوه بر مزایایی مانند توسعه و عیب یابی و بروزرسانی ساده تر و سریع تر و پیچیدگی کمتر، باعث می‌شود تا در فرایند تست و عیب یابی اپ، بیلد سیستم Gradle تنها ماژولی را کامپایل کند که ویرایش و اصلاح شده و مابقی ماژول‌هایی که ویرایش نشده‌اند برای بیلدهای بعدی کش شود که می‌توان کاهش زمان بیلد گریدل را به مقدار زیادی در بیلدهای آتی شاهد بود.<br />
با توجه به مفصل بودن طراحی ماژولار، به همین توضیحات مختصر اکتفا می‌کنم. اگر مایل هستید در مورد آن بیشتر مطالعه کنید از گوگل کمک بگیرید یا مقاله <a href="https://medium.com/google-developer-experts/modularizing-android-applications-9e2d18f244a0" target="_blank" rel="noopener">Modularizing Android Applications</a> را مطالعه کنید.<br />
در این آموزش ۲۱ عامل و روش افزایش سرعت و کاهش زمان بیلد Gradle در اندروید استودیو را بررسی کردیم. امیدوارم مورد مهم دیگری از قلم نیفتاده باشد. چنانچه یک یا چند روش دیگر را تجربه و یا در جایی دیگر مطالعه کرده‌اید، در قسمت دیدگاه‌های همین مبحث در وب سایت اطلاع دهید تا در بروزرسانی بعدی اضافه شود.<br />
موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://androidstudio.googleblog.com/2017/06/android-studio-30-canary-5-is-now.html" target="_blank" rel="noopener">https://androidstudio.googleblog.com/2017/06/android-studio-30-canary-5-is-now.html</a><br />
<a href="https://developer.android.com/studio/build/optimize-your-build" target="_blank" rel="noopener">https://developer.android.com/studio/build/optimize-your-build</a>
</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۱<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۱ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/SpeedUp_Gradle_Build.zip" class="button green edd-submit">دانلود رایگان با حجم ۱ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/SpeedUp_Gradle_Build.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/speed-up-android-studio-gradle-build/">۲۱ روش افزایش سرعت بیلد Gradle در اندروید استودیو</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/speed-up-android-studio-gradle-build/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>فعال کردن امکان نصب برنامه روی کارت حافظه</title>
		<link>https://android-studio.ir/enable-android-app-install-on-sd-card/</link>
					<comments>https://android-studio.ir/enable-android-app-install-on-sd-card/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Wed, 13 Jan 2021 17:46:21 +0000</pubDate>
				<category><![CDATA[آموزش‌های تکمیلی]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=194561</guid>

					<description><![CDATA[<p>حتما می‌دانید در سیستم عامل اندروید، اپلیکیشن‌ها به صورت پیش فرض روی حافظه داخلی دستگاه نصب می‌شوند. ممکن است برخی از کاربران بخصوص آنهایی که دستگاه با سخت افزار ضعیف و حافظه داخلی پایین در اختیار دارند، تمایل داشته باشند برخی از برنامه‌ها را روی کارت حافظه یا همان حافظه خارجی دستگاه خود نصب کنند. [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/enable-android-app-install-on-sd-card/">فعال کردن امکان نصب برنامه روی کارت حافظه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sd/enable_install_on_sd_card_for_our_android_app.png" alt="آموزش فعال کردن امکان نصب برنامه روی کارت حافظه" /></p>
<p>حتما می‌دانید در سیستم عامل اندروید، اپلیکیشن‌ها به صورت پیش فرض روی حافظه داخلی دستگاه نصب می‌شوند. ممکن است برخی از کاربران بخصوص آنهایی که دستگاه با سخت افزار ضعیف و حافظه داخلی پایین در اختیار دارند، تمایل داشته باشند برخی از برنامه‌ها را روی کارت حافظه یا همان حافظه خارجی دستگاه خود نصب کنند. بنابراین فعال کردن امکان نصب برنامه اندرویدی روی کارت حافظه ممکن است عاملی باشد برای جذب بیشتر مخاطب برای اپلیکیشن ما.<br />
در این آموزش به نحوه فعال کردن قابلیت نصب اپلیکیشن بر روی حافظه خارجی دستگاه‌های اندرویدی می‌پردازیم.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید</b></p>
<ul>
<li><a href="#why-install-app-on-external-storage">در چه مواردی نصب برنامه روی کارت حافظه ضرورت دارد؟</a></li>
<li><a href="#define-app-install-location">تعیین محل نصب اپلیکیشن در سیستم عامل اندروید</a>
<ul>
<li><a href="#when-shouldnt-install-app-on-sd-card">در چه مواردی نباید برنامه روی حافظه خارجی نصب شود؟</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="why-install-app-on-external-storage">در چه مواردی نصب برنامه روی کارت حافظه ضرورت دارد؟</h2>
<p>به نام خدا. در حال حاضر شاید به سختی بتوان یک موبایل یا تبلت اندرویدی را در بازار پیدا کرد که حافظه داخلی یا به عبارتی RAM آن کمتر از ۱۶ گیگابایت باشد. هنوز یادم نرفته که اولین گوشی اندرویدی من فقط ۴ گیگابایت حافظه داخلی داشت و دائما با مشکل کمبود حافظه برای نصب برنامه‌ها مواجه بودم.<br />
هرچند با بالا رفتن ظرفیت حافظه داخلی دیوایس‌ها نیاز کمتری به فعال کردن امکان نصب برنامه اندرویدی روی کارت حافظه احساس می‌شود با این حال هنوز هم دیوایس‌های قدیمی با حافظه کمتر از ۸ گیگابایت در دست افراد دیده می‌شود و فعال بودن این قابلیت روی برنامه ما می‌تواند نقش بیشتری در مدیریت حافظه دستگاه این دسته از افراد داشته باشد.<br />
بخصوص در برنامه‌های سنگین و مهمتر از همه در بازی‌ها که معمولا حجم بالاتری را اشغال می‌کنند، این قابلیت بیش از پیش می‌تواند برای ما به عنوان توسعه دهنده و برنامه نویس اندرویدی در دستور کار قرار گیرد.<br />
در ادامه مبحث به نحوه فعالسازی و همچنین جزئیات این قابلیت می‌پردازیم.</p>
<h2 id="define-app-install-location">تعیین محل نصب اپلیکیشن در سیستم عامل اندروید</h2>
<p>توسعه دهندگان <a href="http://android-studio.ir/introduction-android-operating-system/" target="_blank" rel="noopener">سیستم عامل اندروید</a> با انتشار اندروید ۲٫۲ (API 8) قابلیت جدیدی را در اختیار برنامه نویسان قرار دادند که به سادگی می‌توان تنظیمات مربوط به محل نصب برنامه را درون مانیفست تعیین کرد. با توجه به اینکه زمان زیادی از معرفی اندروید ۲٫۲ گذشته و بعید است دستگاهی با اندروید پایینتر از ۴ فعال باشد، از بابت سازگاری این قابلیت با دستگاه‌های موجود در اختیار کاربران جای نگرانی نیست.<br />
برای اینکار کافیست ویژگی android:installLocation را به همراه یک مقدار، درون تگ <manifest> فایل AndroidManifest.xml پروژه اضافه کنیم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sd/add_installLocation_attribute_to_android_project_manifest.png" alt="تعریف ویژگی installLocation در مانیفست" /><figcaption>تعریف ویژگی installLocation در مانیفست</figcaption></figure>
<p><span class="filename">AndroidManifest.xml</span></p>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;ir.android_studio.sdcard&quot;
    android:installLocation=&quot;auto&quot;&gt;

    . . .

&lt;/manifest&gt;
</pre>
<p>همانطور که در تصویر مشاهده می‌کنید برای این ویژگی سه مقدار مشخص شده که بسته به نیازی که داریم می‌توان یک مورد را انتخاب کرد. کاربرد هریک از این ۳ آیتم به شرح زیر است:<br />
<b>Auto:</b> چنانچه برای ویژگی installLocation مقدار auto را انتخاب کنیم، برنامه اندرویدی ما ممکن است روی حافظه خارجی (کارت SD) نصب شود اما تضمینی وجود نداشته و سیستم عامل برای انتخاب محل مناسب برای نصب برنامه، ملاک‌های متعددی را بررسی می‌کند. یکی از این آیتم‌ها، میزان فضای باقیمانده از حافظه داخلی است. یعنی چنانچه حافظه داخلی پر شده و کارت حافظه هم روی دستگاه موجود باشد، به احتمال زیاد برنامه روی حافظه خارجی نصب خواهد شد.<br />
همچنین بعد از نصب برنامه، کاربر می‌تواند در قسمت مدیریت برنامه‌ها در سیستم عامل خود، محل قرارگیری اپلیکیشن را از حافظه داخلی به خارجی و یا بلعکس تغییر دهد. اگر برنامه روی حافظه داخلی نصب شده باشد، کاربر می‌تواند با استفاده از گزینه Move to SD Card آنرا به حافظه خارجی دستگاه منتقل کند.<br />
<b>preferExternal:</b> با تعریف این مقدار برای ویژگی installLocation در مانیفست پروژه اندرویدی، به سیستم عامل اعلام می‌کنیم که ترجیح (prefer) می‌دهیم اپ ما روی حافظه خارجی نصب شود. اما از واژه &#8220;ترجیح&#8221; می‌توان فهمید که ضمانتی در کار نیست و اینکه برنامه ما روی حافظه خارجی نصب شود یا داخلی باز هم به تصمیم سیستم عامل بستگی دارد.<br />
برای مثال چنانچه حافظه خارجی ظرفیت لازم برای قرارگیری فایل برنامه را نداشته باشد، عمل نصب روی حافظه داخلی انجام خواهد شد.<br />
<b>internalOnly:</b> همانطور که از نام آن پیداست، با تعیین این مقدار، امکان نصب اپلیکیشن فقط روی حافظه داخلی امکان پذیر خواهد بود و در آینده هم کاربر امکان انتقال آن به حافظه خارجی را نخواهد داشت.<br />
بنابراین برای فعال کردن امکان نصب برنامه اندرویدی روی کارت حافظه لازم است مقدار preferExternal را انتخاب کنیم. لازم به تاکید است که ضمانتی در خصوص نصب برنامه روی حافظه خارجی وجود ندارد.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در برخی از موبایل‌ها یا تبلت‌های اندرویدی، قابلیت نصب برنامه روی حافظه خارجی حتی با وجود فعال بودن preferExternal در برنامه، توسط سازنده دستگاه غیر فعال شده و حتی پس از نصب روی حافظه داخلی هم امکان انتقال آن به حافظه خارجی وجود ندارد.<br />
البته در تعدادی از دستگاه‌ها، در قسمت ویژه توسعه دهندگان (Developer Options) گزینه‌ای با نام Force allow apps on external وجود دارد که با فعال کردن آن، این امکان میسر خواهد بود:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/sd/force_allow_apps_on_external.png" alt="گزینه Force allow apps on external در Developer Options" /><figcaption>گزینه Force allow apps on external در Developer Options</figcaption></figure>
<p>اما توجه داشته باشید برای کاربران به عنوان مخاطب عام برنامه نمی‌توان روی این گزینه حساب کرد.
</p></div>
<p>لازم به ذکر است با تعیین فضای خارجی به عنوان محل نصب برنامه، فقط فایل APK روی حافظه خارجی قرار گرفته و مابقی موارد مانند داده‌ها، دیتابیس برنامه و سایر موارد باز هم روی حافظه داخلی ذخیره می‌شود.</p>
<h3 id="when-shouldnt-install-app-on-sd-card">در چه مواردی نباید برنامه روی حافظه خارجی نصب شود؟</h3>
<p>چنانچه به هر دلیلی حتی برای چند لحظه حافظه خارجی از دستگاه جدا شود، می‌تواند برخی از عملکردهای برنامه ما را تحت تاثیر قرار داده و باعث بروز مشکل شود.<br />
برای مثال قبلا با <a href="http://android-studio.ir/android-service/" target="_blank" rel="noopener">کاربرد Service ها در اندروید</a> آشنا شدیم. چنانچه در اپ از سرویس‌ها استفاده کرده باشیم، هنگام جدا شدن کارت حافظه از دستگاه، سرویس متوقف شده و با اتصال مجدد آن به دستگاه، سرویس به صورت خودکار مجدد راه اندازی نمی‌شود.<br />
همچنین در صورت استفاده از AlarmManager در برنامه، با جدا شدن کارت حافظه آلارمی که توسط این متد ثبت کرده‌ایم لغو شده و با اتصال مجدد حافظه خارجی، به صورت خودکار فعال نخواهد شد.<br />
به عنوان آخرین مثال، چنانچه بخواهیم با استفاده از <a href="http://android-studio.ir/android-broadcastreceiver-component/" target="_blank" rel="noopener">Broadcast Receiver</a> ها رویداد ACTION_BOOT_COMPLETED را شنود کنیم، با توجه به اینکه پیغام رویداد بوت شدن دستگاه قبل از شناسایی کارت حافظه توسط سیستم عامل ارسال می‌شود، امکان شنود این رویداد در برنامه ما فراهم نخواهد بود.<br />
موفق و پیروز باشید</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developer.android.com/guide/topics/data/install-location" target="_blank" rel="noopener">https://developer.android.com/guide/topics/data/install-location</a>
</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۵<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۱ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/Install_on_sd_card.zip" class="button green edd-submit">دانلود رایگان با حجم ۱ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/Install_on_sd_card.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/enable-android-app-install-on-sd-card/">فعال کردن امکان نصب برنامه روی کارت حافظه</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/enable-android-app-install-on-sd-card/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title>بهینه کردن و محافظت از سورس برنامه با ProGuard/R8</title>
		<link>https://android-studio.ir/android-app-source-protect-and-optimize-using-proguard-r8/</link>
					<comments>https://android-studio.ir/android-app-source-protect-and-optimize-using-proguard-r8/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Sat, 02 Jan 2021 09:56:56 +0000</pubDate>
				<category><![CDATA[آموزش‌های تکمیلی]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=194343</guid>

					<description><![CDATA[<p>یکی از مشکلاتی که اپلیکیشن‌های اندرویدی و صاحبان آن را تهدید می‌کند، سرقت کدهای سورس برنامه توسط سایر افراد است که اصطلاحا دیکد (Decode) نامیده می‌شود. روش مهندسی معکوس باعث می‌شود هر شخص دیگری بتواند کدهای برنامه شما را مشاهده کرده و از آنها استفاده کند. در این آموزش به معرفی روش‌ها و ابزار لازم [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/android-app-source-protect-and-optimize-using-proguard-r8/">بهینه کردن و محافظت از سورس برنامه با ProGuard/R8</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/android_app_protect_source_and_optimizin_using_proguard_r8.png" alt="محافظت از سورس برنامه اندرویدی در برابر دیکد شدن و بهینه کردن آن با ProGuard/R8" /><br />
یکی از مشکلاتی که اپلیکیشن‌های اندرویدی و صاحبان آن را تهدید می‌کند، سرقت کدهای سورس برنامه توسط سایر افراد است که اصطلاحا دیکد (Decode) نامیده می‌شود. روش مهندسی معکوس باعث می‌شود هر شخص دیگری بتواند کدهای برنامه شما را مشاهده کرده و از آنها استفاده کند.<br />
در این آموزش به معرفی روش‌ها و ابزار لازم برای محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد شدن آن توسط فعال کردن ProGuard (پروگارد) و البته جایگزین جدید آن در اندروید استودیو با نام R8 می‌پردازیم. همچنین در مورد بهینه سازی کدهای پروژه اندرویدی و کاهش حجم برنامه که از دیگر وظایف R8 است نیز صحبت خواهیم کرد.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید:</b></p>
<ul>
<li><a href="#protect-android-app">محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد</a></li>
<li><a href="#what-is-proguard">پروگارد (ProGuard) چیست؟</a>
<ul>
<li><a href="#proguard-tasks">ProGuard چه وظایفی را بر عهده دارد؟</a></li>
</ul>
</li>
<li><a href="#R8-replaced-with-proguard">ابزار R8 جایگزینی شایسته برای ProGuard</a>
<ul>
<li><a href="#proguard-vs-r8">مقایسه R8 با ProGuard</a></li>
<li><a href="#R8-advantages">مزایای کامپایلر R8</a></li>
</ul>
</li>
<li><a href="#enable-R8-proguard-in-android-studio">فعال کردن ProGuard / R8 در اندروید استودیو</a>
<ul>
<li><a href="#R8-fullMode">فشرده سازی بیشتر با فعال کردن fullMode در R8</a></li>
<li><a href="#R8-rules">تعریف قوانین سفارشی در R8</a></li>
<li><a href="#R8-important-rules">قوانین R8 / ProGuard</a></li>
</ul>
</li>
<li><a href="#other-tools">ابزار و روش‌های دیگر جهت محافظت از سورس برنامه اندرویدی</a></li>
</ul>
</div>
<h2 id="protect-android-app">محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد</h2>
<p>به نام خدا. قطعا عبارت &#8220;مهندسی معکوس&#8221; را بارها شنیده و خوانده‌اید. مهندسی معکوس یا Reverse Engineering در حوزه صنعت به اینصورت است که یک سخت افزار که قبلا توسط یک شخص/گروه/نهاد و یا دولت ساخته شده توسط شخص یا گروهی دیگر بررسی شده و نمونه مشابه آن با هزینه و صرف وقت کمتر تولید می‌شود.<br />
اما این اتفاق صرفا محدود به ابزار صنعتی و نظامی نیست و در همه حوزه‌ها شاهد وقوع آن هستیم. از جمله در زمینه IT و نرم افزارهایی که برای پلتفرم‌ها و سیستم عامل‌های مختلف توسط گروه‌های متعددی منتشر شده‌اند.<br />
قبلا در مبحث <a href="http://android-studio.ir/introduction-android-operating-system/" target="_blank" rel="noopener">آشنایی با سیستم عامل اندروید</a> گفتیم که برنامه‌های اندرویدی هم مانند هر پلتفرم دیگری برای آنکه قابلیت اجرا روی دستگاه را داشته باشد ابتدا باید کامپایل شوند. زمانی که ما از پروژه خود <a href="http://android-studio.ir/generating-signed-apk/" target="_blank" rel="noopener">خروجی APK یا AAB در اندروید استودیو</a> می‌گیریم عملیات کامپایل شدن سورس برنامه انجام می‌شود.<br />
پس از Compile شدن پروژه اندرویدی، ساختار کدها دگرگون شده و کدها مانند قبل خوانا نیست اما نه به این معنی که قابل برگشت نباشد! برنامه‌های جاوا را می‌توان به سادگی دیکامپایل (Decompile) کرد. کافیست عبارت Java decompiler را در گوگل جستجو کنید.<br />
دیکامپایل کردن برنامه‌های اندروید هم به همین سادگی انجام می‌شود. ابزار معروفی با نام JADX که به صورت رایگان و منبع باز در مخزن گیت هاب در دسترس همگان قرار دارد. حتی شخصی که بخواهد اپلیکیشن شما را دیکامپایل کند نیازی به راه اندازی JADX روی رایانه خود را نیز نداشته و در وب سایتی مانند javadecompilers.com می‌تواند فایل APK اپ را بارگزاری کرده و نسخه دیکد شده را در ظرف چند ثانیه تحویل بگیرد.<br />
البته که نسخه دیکد شده‌ی دریافتی از JADX آماده ایمپورت در <a href="http://android-studio.ir/نصب-اندروید-استودیو/" target="_blank" rel="noopener">محیط توسعه اندروید استودیو</a> نیست اما بهرحال کلاس‌ها و کدهای جاوا قابل دسترسی هستند و استفاده یا بهتر است بگوییم سوء استفاده از آنها را تسهیل می‌کند.<br />
ضمن اینکه لازم به ذکر است با توجه به ماهیت زبان جاوا، ما هیچگاه نمی‌توانیم جلوی دیکد شدن کدها و کلاس‌های جاوا را بگیریم و صرفا باید سعی کنیم خوانایی و درک کدها پس از دیکد شدن برای افراد سخت تر شود. کدهای جاوا پس از دیکامپایل، به byte code ها تبدیل می‌شود نه کدهای native بنابراین دیکد کردن آن به سادگی امکانپذیر است.<br />
در مواردی مانند پرداخت درون برنامه‌ای که اپلیکیشن کد یکتایی را از مارکت اندرویدی برای صحت سنجی سابقه خرید کاربر دریافت می‌کند، هکر می‌تواند این کد را غیر فعال کرده و بدون پرداخت هزینه به شما از امکانات برنامه استفاده کند.<br />
بدترین اتفاق می‌تواند این باشد که شخصی برنامه شما را دیکامپایل کرده و با صرف هزینه و زمانی بسیار کمتر از آنچه شما بهایش را پرداخته‌اید، برنامه‌ای مشابه اپلیکیشن شما ساخته و با نام خودش در مارکت‌ها منتشر کند و حتی با نمایش تبلیغات و یا پرداخت درون برنامه‌ای به کسب درآمد بپردازد!<br />
برای محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد شدن کدهای جاوا و به عبارتی جلوگیری از دیکامپایل شدن اپ، روش‌ها و ابزار متعددی وجود دارد. برخی از ابزار رایگان و برخی دیگر نیاز به پرداخت هزینه لایسنس دارند. در این مبحث تنها به بررسی روش‌های رایج و ابزار رایگان می‌پردازیم و سایر موارد را صرفا به اشاره‌ای اکتفا خواهیم کرد.<br />
Obfuscation به معنی مبهم سازی، اصلی ترین کاری است که به جهت محافظت از سورس برنامه اندرویدی و جلوگیری از دیکد شدن کدهای جاوا انجام می‌شود. در ادامه مبحث به طور مفصل به جزئیات می‌پردازیم.</p>
<h2 id="what-is-proguard">پروگارد (ProGuard) چیست؟</h2>
<p>اگر به محتویات فایل build.gradle (Module:App) پروژه‌های اندرویدی در اندروید استودیو دقت کرده باشید یک بلاک به صورت زیر به صورت پیش فرض وجود دارد:</p>
<pre class="brush: java; title: ; notranslate">
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> کدهایی که درون بلاک buildTypes قرار می‌گیرند تنها هنگام بیلد شدن پروژه اجرا می‌شوند. درون این بلاک یک بلاک دیگر با نام release وجود دارد که کدهای مربوط به پروگارد داخل آن تعریف شده. این کدها هنگامی اجرا خواهند شد که بخواهیم خروجی نسخه release از پروژه خود بگیریم. همچنین اگر بخواهیم کدهای پروگارد یا هر کد موردنظر دیگری را هنگام debug پروژه اجرا کنیم باید بجای بلاکی با نام release در بلاکی با نام debug درون بلاک buildTypes تعریف شود.
</div>
<p>فعال کردن ProGuard در اندروید استودیو بسیار ساده است. پروگارد در بیلد سیستم Gradle قرار داده شده و نیاز به نصب جداگانه ندارد. در حالت پیش فرض برای minifyEnabled مقدار false تعریف شده. این آیتم برای تعیین فعال یا غیر فعال بودن Proguard هنگام کامپایل شدن پروژه استفاده می‌شود. ترجمه تحت الفظی آن می‌شود &#8220;کوچک کردن فعال است&#8221;. چنانچه مقدار را به true تغییر دهیم، پروگارد فعال شده و هنگام گرفتن خروجی پروژه (release یا debug) وظایف خودش را انجام خواهد داد.<br />
در خط دوم، فایل تنظیمات و قوانین پیش فرض پروگارد توسط proguardFiles تعیین شده است.</p>
<h3 id="proguard-tasks">ProGuard چه وظایفی را بر عهده دارد؟</h3>
<p>در صورت فعال کردن پروگارد در پروژه اندرویدی در اندروید استودیو، سه عمل در هنگام کامپایل شدن انجام می‌شود که به شرح زیر می‌باشد:<br />
<b>مبهم سازی نام‌ها یا Name obfuscation:</b> تغییر نام کلاس‌ها، فیلدها و متدها با نام‌های کوتاه و عموما یک کاراکتری. برای مثال ممکن است نام کلاس Users به a تغییر پیدا کند که علاوه بر کاهش تعداد کاراکترهای نام کلاس هنگام فراخوانی و به دنبال آن کاهش حجم نهایی کدها، خوانایی، درک و تشخیص کدها را تا حدود زیادی مشکل می‌کند.<br />
<b>Shrinking یا Tree shaking:</b> این قابلیت باعث می‌شود تا کلاس‌ها، متدها، فیلدها، ویژگی‌ها (attributes) و کتابخانه‌های بلا استفاده درون پروژه هنگام کامپایل حذف شوند که باز هم کاهش حجم نهایی اپ را بدنبال خواهد داشت. بنابراین این قابلیت به بهینه شدن و افزایش سرعت اجرای برنامه روی دستگاه کاربر کمک می‌کند.<br />
<b>بهینه سازی کد یا Code optimization:</b> در هر دو مورد قبل عمل بهینه کردن کدها انجام می‌شد اما هنوز هم برای کمتر شدن حجم کدها جای کار هست. وظیفه دیگر پروگارد بهینه کردن کدهای جاوا است. به عبارت دیگر، کدها بازنویسی می‌شوند تا در حد امکان تعداد کاراکترها و دستورات کاهش یافته و حجم فایل DEX اپ به کمترین میزان ممکن برسد.<br />
به عبارتی کدها Minify می‌شوند. درست مانند آنچه در Minify کردن کدهای CSS صفحات وب اتفاق می‌افتد. هنگام Minify کردن یک کد CSS عملیاتی مانند حذف فاصله‌های اضافی و کامنت‌ها انجام می‌شود. همچنین برای مثال اگر برای یک قسمت از صفحه وب، رنگ <span dir="ltr">#FFFFFF</span> تعریف شده باشد، آنرا به <span dir="ltr">#FFF</span> تغییر می‌دهد. همین تغییرات کوچک و به ظاهر غیر ضرور، در نهایت حجم قابل توجهی از فایل نهایی CSS را کاهش می‌دهد.<br />
برای مثال اگر در بخشی از پروژه اندرویدی یک if/else تعریف کرده باشیم و پروگارد تشخیص دهد بلاک <span dir="ltr">else{}</span> هیچگاه اجرا نخواهد شد، آنرا از خروجی برنامه حذف می‌کند. یا مثلا اگر متدی تعریف کرده باشیم که فقط از یک جا فراخوانی می‌شود، پروگارد آنرا حذف کرده و به صورت inline در محل کد اصلی جایگزین می‌کند.<br />
به این ترتیب درصدی از کدهای اضافی پروژه اندرویدی ما حذف شده که علاوه بر کاهش حجم نهایی فایل APK یا AAB برنامه، با بهینه شدن دستورات و کلاس‌های جاوا، سرعت اجرای برنامه می‌تواند افزایش یابد. پروگارد می‌تواند تا ۵۰% از حجم بایت کدهای اپلیکیشن اندرویدی را کاهش دهد.<br />
خب! تا حد زیادی با ProGuard و کاربردهای آن آشنا شدیم. اما لازم است بگویم که پس از انتشار Android Gradle plugin 3.4.0 و همزمان با انتشار Android studio 3.3 beta (در ماه April سال ۲۰۱۹) جایگزینی جدید با نام R8 توسط توسعه دهندگان اندروید استودیو برای ProGuard معرفی شد. این ابزار جدید نسبت به نسخه قبلی برتریی‌هایی را داشته و خروجی بهینه تری را برای ما رقم می‌زند. البته جای هیچ نگرانی نیست زیرا طریق فعالسازی و استفاده آن تفاوتی با پروگارد ندارد.<br />
در واقع R8 از ترکیب و ادغام ProGuard با ابزار دیگر ساخته شده و از قوانین آن پیروی می‌کند. در ادامه بیشتر با R8 آشنا می‌شویم.</p>
<h2 id="R8-replaced-with-proguard">ابزار R8 جایگزینی شایسته برای ProGuard</h2>
<p>همانطور که قبلا اشاره شد، با انتشار پلاگین گریدل نسخه ۳٫۴٫۰ برای اندروید استودیو، ابزار معروف ProGuard بازنشسته شد و جای خود را به ابزاری تازه نفس و قدرتمندتر به نام R8 داد. ابزار R8 نسبت به پروگارد، عملیات بهینه سازی را با سرعت بالاتری انجام داده و علاوه بر آن، درصد بهینه سازی و کاهش حجم فایل نصبی برنامه نیز افزایش می‌دهد.<br />
شاید بهتر باشد مزایای R8 را نسبت به ProGuard به صورت دقیق‌تر و با جزئیات بیشتری بررسی کنیم.</p>
<h3 id="proguard-vs-r8">مقایسه R8 با ProGuard</h3>
<p>لازم می‌دانم از کلی گویی اجتناب کرده و تفاوت‌های این دو را به لحاظ فنی و دقیق برای شما تشریح کنم تا درک بهتری از تصمیم گوگل برای جایگزینی R8 داشته باشید. البته عنوان مقایسه خیلی برای این قسمت کامل نیست. می‌خواهم تاریخچه آنچه در طی این چند سال توسعه دهندگان اندروید را از ProGuard به R8 رسانده بیان کنم.<br />
در گذشته روش کار به اینصورت بود که کامپایلر جاوا، سورس کدهای جاوای پروژه اندرویدی را به بایت کد (Bytecode) های جاوا تبدیل می‌کرد. سپس این بایت کدها توسط ProGuard بهینه شده و بایت کدهای بهینه‌ای ایجاد می‌شد که سریعتر و کم حجم تر بودند. در نهایت، کامپایلر DX این بایت کدها را به بایت کدهای ویژه ماشین مجازی دالویک (Dalvik) تبدیل می‌کرد. قبلا در مبحث آشنایی با سیستم عامل اندروید با کاربرد ماشین‌های مجازی Dalvik و ART آشنا شدیم.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/android_dex_compiler.png" alt="کامپایلر DEX اندروید" /><figcaption>کامپایلر DEX اندروید استودیو</figcaption></figure>
<p>این بایت کد که با فرمت <span dir="ltr">.dex</span> در پکیج فایل نصبی APK نگهداری می‌شود بسته به نسخه سیستم عامل اندروید دستگاه کاربر، توسط ماشین مجازی Dalvik یا ART و یا ترکیبی از هردو (مانند Android P) به زبان قابل فهم برای ماشین ترجمه می‌شد.<br />
این فرآیند قدری زمان بر بود و در سال ۲۰۱۵ تیم توسعه اندروید تصمیم گرفت کامپایلر جدیدی را جایگزین کند تا مراحل کامپایل کاهش یابد. بنابراین کامپایلری با نام Jack &#038; Jill معرفی شد.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/android_jack_and_jill_compiler.png" alt="کامپایلر Jack &#038; Jill" /><figcaption>کامپایلر Jack &#038; Jill اندروید استودیو</figcaption></figure>
<p>در واقع این کامپایلر ترکیبی از کامپایلر جاوا، پروگارد و کامپایلر دالویک بود و دستورات و توابع هرسه در یک مرحله انجام می‌شد که نتیجه آن افزایش سرعت کامپایل پروژه اندرویدی بود. اما Jack &#038; Jill هم خالی از ایراد نبود و در تعامل با برخی بایت کدهای Java مشکلاتی داشت. به همین دلیل تیم توسعه اندروید این کامپایلر را در سال ۲۰۱۷ کنار گذاشت.<br />
سپس کامپایلر دیگری با نام D8 جایگزین شد که از حیث تعداد مراحل تفاوتی با حالت اول نداشت و صرفا در مرحله آخر، D8 جایگزین DX شده بود.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/android_d8_compiler.png" alt="کامپایلر D8" /><figcaption>کامپایلر D8 اندروید استودیو</figcaption></figure>
<p>کامپایلر D8 سازگاری بیشتر بخصوص با کاتلین داشت. علاوه بر آن بایت کدهای کمتر و بهینه تری را تولید می‌کرد.<br />
در نهایت R8 معرفی شد که از ادغام ProGuard و D8 بوجود آمده است.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/android_r8_compiler.png" alt="ابزار R8 اندروید استودیو برای مبهم سازی، بهینه سازی و کوچک کردن کدهای برنامه اندرویدی" /><figcaption>کامپایلر R8 اندروید استودیو</figcaption></figure>
<p>در حال حاضر، بایت کدهای جاوا مستقیما توسط کامپایلر R8 به بایت کدهای بهینه دالویک تبدیل می‌شوند. مراحل کمتر، بهینه بودن و سازگاری بیشتر با نیازهای جدید.</p>
<h3 id="R8-advantages">مزایای کامپایلر R8</h3>
<p>از مهمترین دلایلی که گوگل را مجاب کرد تا ابزار کامپایلر R8 را جایگزین ProGuard در اندروید استودیو کند می‌توان به موارد زیر اشاره کرد:<br />
<b>بهینه سازی بیشتر:</b> R8 در مقایسه با ProGuard بهینه سازی و Minification عمیق‌تری انجام می‌دهد که نتیجه آن فشرده شدنِ بیشترِ نسخه خروجی یعنی فایل APK است. به عبارتی می‌توانیم چند درصد دیگر هم کاهش حجم را هنگام کامپایل کردن پروژه اندرویدی خود شاهد باشیم.<br />
بر اساس تست‌هایی که انجام شده R8 حتی می‌تواند تا ۷۰ درصد از حجم برنامه را نسبت به حالت عادی فشرده‌تر کند که یک خروجی فوق العاده به حساب می‌آید. حتی ۱ درصد کاهش حجم بخصوص در برنامه‌های با حجم بالا می‌تواند در تسریع دسترسی کاربران به اپلیکیشن هنگام دریافت آن از مارکت‌ها و همچنین اجرای برنامه روی دستگاه نقش موثری داشته باشد.<br />
<b>سازگاری بیشتر با Kotlin:</b> با پشتیبانی رسمی محیط توسعه اندروید استودیو از زبان محبوب کاتلین در نسخه‌های جدید، سازگاری هرچه بیشتر کامپایلر با کدهای Kotlin ضروری بود. R8 این ویژگی را دارد و در بهینه کردن کدهایی که به زبان کاتلین نوشته شده‌اند بازدهی بالاتری نسبت به پروگارد دارد.<br />
<b>خروجی بهتر:</b> R8 خروجی بهتری نسبت به پروگارد به ما تحویل می‌دهد. علاوه بر آن، سرعت بیلد شدن پروژه نیز به نسبت قبل مقداری کاهش می‌یابد.</p>
<h2 id="enable-R8-proguard-in-android-studio">فعال کردن ProGuard / R8 در اندروید استودیو</h2>
<p>به اندازه کافی در خصوص پروگارد و جایگزین جدید آن یعنی R8 به تئوریات پرداختیم. بهتر است روش فعالسازی و همچنین جزئیات آن را بررسی کنیم. البته در اینجا می‌خواهیم خروجی پروژه را قبل و بعد از فعال کردن R8 بررسی کنیم تا نتیجه کار را در عمل ببینیم.<br />
مطابق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام My Application می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
یک متغیر از نوع String درون اکتیویتی پیش فرض پروژه تعریف می‌کنم:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    String myString;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myString = &quot;android-studio.ir&quot;;

    }
}
</pre>
<p>از مسیر Build > Generate Signed Bundle/APK یک خروجی APK از پروژه می‌گیرم.<br />
بعد از ساخته شدن فایل، آنرا تغییر نام می‌دهم تا از خروجی دوم قابل تشخیص باشد. حالا فایل build.gradle (app) پروژه را باز کرده و در بلاک release برای minifiEnabled مقدار true را جایگزین false می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
</pre>
<div class="alert alert-warning">
<span class="notice">نکته:</span> در ابتدای مبحث هم اشاره شد که R8 از قوانین ProGuard استفاده می‌کند بنابراین آیتم proguardFiles نباید ابهامی در خصوص اینکه آیا R8 فعال است یا ProGuard برایتان ایجاد کند. البته به شرط آنکه از اندروید استودیو ۳٫۴ به بالا استفاده می‌کنید.
</div>
<div class="alert alert-warning">
<span class="notice">نکته:</span> با انتشار نسخه ۳٫۴ اندروید استودیو و معرفی R8 بجای پروگارد، ابتدا برای فعالسازی آن لازم بود کد زیر در gradle.properties تعریف شود:</p>
<pre class="brush: java; title: ; notranslate">
android.enableR8=true
</pre>
<p>اما در نسخه‌های جدید اندروید استودیو این کد کارایی نداشته و صرفا باید مقدار minifyEnabled به true تغییر داده شود.
</p></div>
<p>مجدد یک خروجی APK از پروژه می‌سازم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/apk_size_before_vs_after_r8_proguard_enabled.png" alt="مقایسه حجم اپلیکیشن اندرویدی قبل و بعد از فعالسازی ProGuard یا R8" /><figcaption>حجم فایل APK قبل و بعد از فعالسازی ProGuard/R8</figcaption></figure>
<p>در تصویر بالا مشاهده می‌کنید حجم فایل APK که در مرحله اول ساخته شده ۲۲۲۱ کیلوبایت است در صورتی که فایل دوم ۱۴۷۰ کیلوبایت حجم دارد. یعنی با فعال شدن R8 در حین کامپایل برنامه چیزی حدود ۳۰ درصد از حجم فایل نصبی برنامه کاهش یافت.<br />
اما همانطور که مفصل بحث کردیم، در کنار بهینه سازی کدها و کاهش حجم نهایی، یک وظیفه دیگر هم بر عهده R8 هست؛ یعنی همان مبهم سازی کدهای جاوا به جهت محافظت از سورس برنامه اندرویدی که در نهایت باعث جلوگیری از دیکد شدن کلاس‌ها، متدها و فیلدها می‌شود.<br />
البته لازم به تکرار است وقتی صحبت از جلوگیری از دیکامپایل شدن اپ می‌کنیم منظور سخت کردن فرایند است وگرنه بطور کامل نمی‌توان از دیکد کردن جاوا و دیکامپایل شدن اپلیکیشن اندرویدی جلوگیری کرد.<br />
برای تست و بررسی فعال شدن R8 در پروژه، هردو فایل APK را توسط <a href="http://www.javadecompilers.com/apk" rel="nofollow noopener" target="_blank">APK Decompiler</a> بصورت آنلاین دیکامپایل می‌کنم. این سرویس رایگان و آنلاین از <a href="https://github.com/skylot/jadx" target="_blank" rel="noopener">JADX</a> برای دیکامپایل کردن فایل dex موجود در پکیج برنامه اندرویدی و تبدیل آن به کلاس‌های Java استفاده می‌کند.</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/decompile_apk_using_jadx_dex_decompiler.png" alt="جلوگیری از دیکامپایل اپ اندرویدی توسط ابزاری مانند JADX یا ApkTool" /></p>
<p>کافیست فایل apk را انتخاب کرده و روی گزینه Upload and Decompile کلیک کنم تا در ظرف مدت چند ثانیه فایل zip سورس برنامه را تحویل دهد!</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/decompile_apk_using_jadx_dex_decompiler_2.png" alt="دیکامپایل فایل APK توسط ابزار آنلاین" /><figcaption>دیکامپایل فایل APK توسط ابزار آنلاین</figcaption></figure>
<p>این کار را برای هردو فایل انجام می‌دهم. فایل‌های zip را باز کرده و بررسی می‌کنم. برای مثال تفاوت کلاس MainActivity به ترتیب، قبل و بعد از فعال شدن R8 به اینصورت است:</p>
<pre class="brush: java; title: ; notranslate">
package com.example.myapplication;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    String myString;

    /* access modifiers changed from: protected */
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) C0400R.layout.activity_main);
        this.myString = &quot;android-studio.ir&quot;;
    }
}
</pre>
<pre class="brush: java; title: ; notranslate">
package com.example.myapplication;

import android.os.Bundle;
import p002b.p004b.p005c.C0149h;

public class MainActivity extends C0149h {
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
    }
}
</pre>
<p>ملاحظه می‌کنید علاوه بر حذف خط کامنت از کلاس، نام کلاس کتابخانه</p>
<pre class="brush: java; title: ; notranslate">
androidx.appcompat.app.AppCompatActivity
</pre>
<p>به</p>
<pre class="brush: java; title: ; notranslate">
p002b.p004b.p005c.C0149h
</pre>
<p>تغییر یافته. همچنین String ای که داخل اکتیویتی تعریف شده بود نیز حذف شده زیرا R8 تشخیص داده که این متغیر بلا استفاده بوده و در هیچ قسمتی از برنامه فراخوانی نشده است.<br />
نتیجه‌ی این تغییر، کاهش حجم کد از ۴۲۵ کاراکتر به ۲۷۵ کاراکتر و همچنین سخت شدن درک کدها شده است.</p>
<h3 id="R8-fullMode">فشرده سازی بیشتر با فعال کردن fullMode در R8</h3>
<p>اما R8 هنوز هم می‌تواند اپلیکیشن ما را فشرده تر کند. کافیست در فایل gradle.properties خط زیر را اضافه کنیم:</p>
<pre class="brush: java; title: ; notranslate">
android.enableR8.fullMode=true
</pre>
<p>توجه داشته باشید این فایل مختص پروژه فعلی نیست و تنظیمات آن در همه‌ی پروژه‌های شما اعمال می‌شود. بنابراین اگر در پروژه دیگری نیاز به فعالسازی fullMode نباشد لازم است این خط را حذف یا کامنت کنیم.</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/enable_R8_fullMode_in_android_studio.png" alt="فعال کردن قابلیت fullMode در R8 برای بهینه سازی بیشتر در اندروید استودیو" /><figcaption>فعال کردن قابلیت fullMode در R8</figcaption></figure>
<p>مجددا یک خروجی APK از پروژه می‌گیرم تا تفاوت حجم برنامه قبل و بعد از فعال کردن حالت fullMode را ارزیابی کنیم:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/R8_fullMode_result.png" alt="کاهش حجم بیشتر در R8 توسط قابلیت fullMode" /><figcaption>کاهش بیشتر حجم پس از فعالسازی fullMode</figcaption></figure>
<p>مشاهده می‌کنید بازهم حدود ۱۰۰ کیلوبایت از حجم نهایی اپلیکیشن کاسته شد. به عبارت دیگر با فعال کردن R8 و همچنین حالت fullMode حدود ۴۰ درصد کاهش حجم را در خروجی پروژه شاهد بودیم. البته این اعداد ثابت نیست و بسته به مقدار کدها، تعداد کتابخانه‌های بکار رفته در پروژه، منابع موجود در پروژه مانند تصاویر، فایل‌های صوتی و&#8230; متغیر خواهد بود.</p>
<h3 id="R8-rules">تعریف قوانین سفارشی در R8</h3>
<p>با فعال کردن ProGuard/R8 در پروژه اندرویدی، هرسه عملکردی که قبلا به آنها اشاره کردیم روی سورس پروژه شامل کلاس‌ها، متدها و فیلدها اعمال می‌شوند. اما گاهی اوقات لازم است یک آیتم را به عنوان استثناء در R8 تعریف کنیم تا هیچگونه تغییری روی آن انجام نگردد.<br />
برای مثال در <a href="http://android-studio.ir/retrofit-with-php-mysql-web-service/" target="_blank" rel="noopener">آموزش کار با کتابخانه Retrofit</a> که شامل یک فرم عضویت بود و اطلاعات شخص شامل نام، نام کاربری و رمز عبور به سرور ارسال می‌شد، نام متغیرهای ارسالی نباید دچار هیچگونه تغییراتی شود در غیر اینصورت اطلاعات عضویت شخص در دیتابیس ثبت نخواهد شد.<br />
اما راهکار چیست؟ آیا به دلیل جلوگیری از بروز اشکال در یک کتابخانه یا کلاس یا تابع باید از بهینه کردن و محافظت از سورس برنامه اندرویدی و جلوگیری از دیکامپایل شدن اپ چشم پوشی کنیم؟ خیـــــر!<br />
در ProGuard و جایگزین آن یعنی R8 قابلیتی در اختیار توسعه دهنده و برنامه نویس اندرویدی قرار گرفته که می‌تواند برای آیتم‌های خاص استثناء قائل شد تا بدون هیچگونه مبهم سازی و یا بهینه سازی در نسخه کامپایل شده برنامه قرار گیرند.<br />
دوباره به بلاک release دقت کنید:</p>
<pre class="brush: java; title: ; notranslate">
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
</pre>
<p>قوانین و عملکردهای پایه و پیش فرض پروگارد (R8) توسط getDefaultProguardFile از فایلی با نام proguard-android-optimize.txt خوانده می‌شود. اما چنانچه بخواهیم استثنائی در قوانین تعریف کنیم نیازی به ویرایش فایل پیش فرض نیست و لازم است قوانین اختصاصی پروژه را در فایل proguard-rules.pro اضافه کنیم.<br />
این فایل در کنار سایر فایل‌های زیر مجموعه Gradle قرار دارد:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/add_ProGuard_R8_custom_rules_to_proguard-rules.pro.png" alt="تعریف قوانین سفارشی برای ProGuard/R8 در فایل proguard-rules.pro" /><figcaption>محل تعریف قوانین سفارشی برای ProGuard/R8</figcaption></figure>
<p>این فایل به صورت پیش فرض دارای هیچ قانون فعالی نیست و صرفا چند خط توضیحات به صورت کامنت قید شده است. البته در بین توضیحات، چند مورد از قوانین پر کاربرد هم ذکر شده که در صورت نیاز کافیست از حالت کامنت خارج شود:</p>
<p><span class="filename">proguard-rules.pro</span></p>
<pre class="brush: java; title: ; notranslate">
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

برای مثال چنانچه بخواهیم از WebView در برنامه خود استفاده کنیم و صفحه وب موردنظر حاوی کدهای JS (جاوا اسکریپت) باشد، قانونی که بعد از توضیحات مربوط به وب ویو قرار گرفته را از حالت کامنت خارج می‌کنیم:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}
</pre>
<p>معمولا کتابخانه‌هایی که نیاز به تعریف قوانین اختصاصی دارند توضیحات لازم را در صفحه معرفی خود قید می‌کنند. از جمله Retrofit:</p>
<figure>
<img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/r8/proguard_R8_rules_for_retrofit_library.png" alt="قوانین سفارشی کتابخانه Retrofit برای ProGuard/R8" /><figcaption>قوانین سفارشی کتابخانه Retrofit برای ProGuard/R8</figcaption></figure>
<p>ملاحظه می‌کنید در قسمت توضیحات مربوط به قوانین R8 / ProGuard سه لینک ذکر شده که مربوط به قوانین Retrofit و OkHttp و Okio هستند. از آنجایی که کتابخانه‌های OkHttp و Okio در داخل کتابخانه Retrofit قرار دارند، لازم است قوانین هرسه مورد به پروژه اضافه شود.<br />
من برای بررسی بیشتر قوانین، موارد مربوط به خود رتروفیت را اضافه می‌کنم:</p>
<p><span class="filename">proguard-rules.pro</span></p>
<pre class="brush: java; title: ; notranslate">
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* &lt;methods&gt;;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* &lt;methods&gt;; }
-keep,allowobfuscation interface &lt;1&gt;
</pre>
<p>اگر این قوانین برایتان مبهم بنظر می‌رسند جای نگرانی نیست؛ در اینجا به موارد پر کاربرد اشاره می‌کنیم.</p>
<h3 id="R8-important-rules">قوانین R8 / ProGuard</h3>
<p>مهمترین قوانین R8/ProGuard را به طور مختصر توضیح می‌دهم:<br />
<b>-keepattributes:</b> ویژگی‌هایی که توسط این قانون تعریف شوند بدون تغییر باقی می‌مانند.<br />
<b>-keep:</b> توسط این دستور می‌توان یک کلاس و متدها و فیلدهای داخل آن را به عنوان استثناء تعریف کرد تا عملکردهای R8 روی آن پیاده نشود.<br />
<b>-keepclassmembers:</b> متدها و فیلدهای موردنظر داخل یک کلاس را می‌توان به عنوان استثناء تعریف نمود.<br />
<b>-keepnames:</b> از تغییر نام کلاس یا متدها و فیلدهای مدنظر جلوگیری می‌کند.<br />
<b>-dontwarn:</b> تعیین می‌کند که هیچگونه هشداری در مورد ارجاعاتی (References) که در کلاس مدنظر ذکر شده و ProGuard/R8 آن را پیدا نمی‌کند و یا سایر خطاها، داده نشده و از آنها عبور کند.<br />
<b>-dontshrink:</b> این قانون، قابلیت Shrink یعنی حذف آیتم‌های بلا استفاده را غیر فعال می‌کند و صرفا دو عمل دیگر یعنی بهینه سازی (Optimization) و مبهم سازی (Obfuscation) برای آن انجام می‌شود.<br />
<b>-dontoptimize:</b> مانند مورد قبل با این تفاوت که فقط عمل بهینه سازی انجام نمی‌شود.</p>
<p>قوانین پروگارد گسترده تر از چند موردی است که در اینجا اشاره شد. برای مطالعه لیست کامل قوانین و همچنین توضیحات بیشتر به وب سایت آن در صفحه <a href="https://www.guardsquare.com/en/products/proguard/manual/refcard" target="_blank" rel="noopener">ProGuard Reference card</a> مراجعه کنید.</p>
<div class="alert alert-warning">
<span class="notice">نکته:</span> با توجه به تغییرات زیادی که توسط R8 در سورس پروژه انجام می‌شود لازم است قبل از انتشار نسخه نهایی اپ جهت ارائه به کاربران، تمامی قسمت‌های آن به دقت تست و بررسی شده و در صورت نیاز، قوانین لازم برای کتابخانه‌ها و کلاس‌ها تعریف شود.
</div>
<h2 id="other-tools">ابزار و روش‌های دیگر جهت محافظت از سورس برنامه اندرویدی</h2>
<p>تا اینجا توانستیم با استفاده از ابزار داخلی و رایگان اندروید استودیو تا حد زیادی عمل مبهم سازی و بهینه سازی فایل APK را انجام دهیم. اما جلوگیری از دیکد شدن سورس کد برنامه اندرویدی محدود به همین R8 نیست.<br />
R8 یا ProGuard می‌تواند عملیات مبهم سازی را بر روی کلاس‌ها، فیلدها و متدها پیاده سازی کرده تا درک کدها و سوء استفاده از آنها به راحتی امکان پذیر نباشد. اما این ابزار String ها را بدون تغییر باقی می‌گذارد که در مواردی می‌تواند امنیت برنامه ما را بخطر بیندازد.<br />
برای مثال چنانچه اپلیکیشن شما به یک سرور یا API وابسته است و اطلاعاتی بین آنها ردوبدل می‌شود، در صورتی که آدرس (URL) وب سرویس یا API رمزگذاری (Encrypt) نشده باشد هکر می‌تواند با دیکامپایل کردن برنامه به URL یا کلیدهای خصوصی مربوط به API دسترسی پیدا کرده و از آنها برای مقاصد خود استفاده کند.<br />
چنانچه به قابلیت رمزگذاری رشته‌های متنی برای اپ خود نیاز داشته باشیم لازم است از جایگزین‌های R8 استفاده کرده یا ابزار دیگری را در کنار آن اضافه کنیم.<br />
برای مثال شرکت سازنده ProGuard یعنی GuardSquare یک ابزار دیگر با نام <a href="https://www.guardsquare.com/en/products/dexguard" target="_blank" rel="noopener">DexGuard</a> را معرفی کرده که امکانات بیشتری نسبت به پروگارد را در اختیار برنامه نویسان اندرویدی قرار می‌دهد. یکی از امکانات دکس گارد رمزگذاری String هاست. البته این ابزار بر خلاف پروگارد رایگان نبوده و باید لایسنس آن خریداری شود.<br />
اما بر خلاف DexGuard که باید جایگزین ProGuard/R8 شود و رایگان هم نیست، پلاگین‌هایی هستند که رایگان بوده و در کنار R8 و به عنوان مکمل استفاده می‌شوند. از جمله <a href="https://github.com/christopherney/Enigma" target="_blank" rel="noopener">پلاگین Enigma</a> یا <a href="https://github.com/StringCare/AndroidLibrary" target="_blank" rel="noopener">StringCare</a> که هردو رایگان هستند.<br />
البته نوشتن کدهای حساس و مهم برنامه به صورت native به زبان C/C++ هم می‌تواند فرایند مهندسی معکوس را به مراتب دشوار تر از قبل کند. با استفاده از ابزار NDK می‌توانید کدهای نیتیو خود را در قالب فایل‌های  <span dir="ltr">.so</span> به پروژه اندرویدی اضافه کنید.<br />
پرداختن به این ابزار از حوصله این مبحث خارج بوده و صرفا به ذکر نام آنها اکتفا می‌کنم. به امید خدا در آموزش‌های آتی به معرفی کامل و نحوه فعالسازی یکی از آنها خواهیم پرداخت.<br />
موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://www.guardsquare.com/en/blog/proguard-and-r8" target="_blank" rel="noopener">https://www.guardsquare.com/en/blog/proguard-and-r8</a><br />
<a href="https://www.guardsquare.com/en/blog/comparison-proguard-vs-r8-october-2019-edition" target="_blank" rel="noopener">https://www.guardsquare.com/en/blog/comparison-proguard-vs-r8-october-2019-edition</a><br />
<a href="https://developer.android.com/studio/build/shrink-code" target="_blank" rel="noopener">https://developer.android.com/studio/build/shrink-code</a><br />
<a href="https://www.guardsquare.com/en/blog/dexguard-vs-proguard" target="_blank" rel="noopener">https://www.guardsquare.com/en/blog/dexguard-vs-proguard</a><br />
<a href="https://blog.mindorks.com/applying-proguard-in-an-android-application" target="_blank" rel="noopener">https://blog.mindorks.com/applying-proguard-in-an-android-application</a><br />
<a href="https://android-developers.googleblog.com/2018/11/r8-new-code-shrinker-from-google-is.html" target="_blank" rel="noopener">https://android-developers.googleblog.com/2018/11/r8-new-code-shrinker-from-google-is.html</a>
</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۲۰<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۲ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/Proguard_R8.zip" class="button green edd-submit">دانلود رایگان با حجم ۲ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/Proguard_R8.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/android-app-source-protect-and-optimize-using-proguard-r8/">بهینه کردن و محافظت از سورس برنامه با ProGuard/R8</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/android-app-source-protect-and-optimize-using-proguard-r8/feed/</wfw:commentRss>
			<slash:comments>11</slash:comments>
		
		
			</item>
		<item>
		<title>بررسی نصب بودن برنامه در اندروید</title>
		<link>https://android-studio.ir/check-if-an-android-application-is-installed-or-not/</link>
					<comments>https://android-studio.ir/check-if-an-android-application-is-installed-or-not/#comments</comments>
		
		<dc:creator><![CDATA[سیدمهدی مطهری]]></dc:creator>
		<pubDate>Tue, 06 Oct 2020 16:05:04 +0000</pubDate>
				<category><![CDATA[آموزش‌های تکمیلی]]></category>
		<category><![CDATA[آموزش‌های رایگان]]></category>
		<guid isPermaLink="false">https://android-studio.ir/?p=191320</guid>

					<description><![CDATA[<p>در این جلسه از سری مباحث آموزش برنامه نویسی اندروید قصد دارم نحوه کنترل و بررسی نصب یا عدم نصب بودن برنامه در اندروید را با دو روش متفاوت بررسی کنم. به اینصورت که چک می‌کنیم آیا اپلیکیشنی با Package name مدنظر ما روی دستگاه اندرویدی قبلا نصب شده یا خیر. همچنین در ادامه تعریف [&#8230;]</p>
<p>نوشته <a href="https://android-studio.ir/check-if-an-android-application-is-installed-or-not/">بررسی نصب بودن برنامه در اندروید</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/appinstalled/check_if_an_application_is_installed_or_not_on_android_device.png" alt="بررسی نصب بودن برنامه در اندروید" /><br />
در این جلسه از سری مباحث <a href="http://android-studio.ir" target="_blank" rel="noopener noreferrer">آموزش برنامه نویسی اندروید</a> قصد دارم نحوه کنترل و بررسی نصب یا عدم نصب بودن برنامه در اندروید را با دو روش متفاوت بررسی کنم. به اینصورت که چک می‌کنیم آیا اپلیکیشنی با Package name مدنظر ما روی دستگاه اندرویدی قبلا نصب شده یا خیر.<br />
همچنین در ادامه تعریف می‌کنیم چنانچه برنامه مدنظر روی سیستم عامل موجود نبود، صفحه مربوط به نصب نرم افزار در مارکت اندرویدی مانند پلی استور، کافه بازار و&#8230; و یا لینک آنرا در صفحه مرورگر پیش فرض باز کند.</p>
<div class="title-box">
<b>آنچه در این آموزش می‌خوانید:</b></p>
<ul>
<li><a href="#check-if-an-application-is-installed-or-not">در چه مواردی به بررسی نصب بودن برنامه در اندروید نیاز داریم؟</a></li>
<li><a href="#create-project-on-android-studio">ساخت پروژه بررسی نصب بودن برنامه اندرویدی</a>
<ul>
<li><a href="#using-getPackageInfo-method">روش اول: استفاده از متد getPackageInfo</a></li>
<li><a href="#using-intent">روش دوم: استفاده از intent</a></li>
<li><a href="#open-app-store-or-a-web-page">هدایت کاربر به صفحه نصب برنامه در مارکت یا یک صفحه وب</a></li>
</ul>
</li>
</ul>
</div>
<h2 id="check-if-an-application-is-installed-or-not">در چه مواردی به بررسی نصب بودن برنامه در اندروید نیاز داریم؟</h2>
<p>به نام خدا. گاهی اوقات بخشی از فرایندهای موجود در اپلیکیشن ما به یک یا چند برنامه دیگر وابسته است که در صورت نصب نبودن برنامه موردنظر روی دستگاه اندرویدی کاربر، می‌تواند سبب بروز اختلال در عملکرد برنامه ما گردد.<br />
فرض کنید قصد توسعه و برنامه نویسی اپلیکیشنی را دارید که بخشی از امکانات آن با پرداخت درون برنامه‌ای از طریق مارکت‌های اندرویدی فعال می‌شود. در اینجا چنانچه مارکت موردنظر شما (مانند بازار، مایکت و&#8230;) روی دستگاه کاربر از قبل نصب و فعال نشده باشد، عملیات پرداخت ناتمام مانده و برنامه کرش خواهد کرد.<br />
برای حل این مشکل لازم است شرطی را در برنامه تعریف کنیم که قبل از شروع فرایند پرداخت درون برنامه‌ای، بررسی کند صرفا در صورتی این عملیات آغاز شود که مارکت با Package name مدنظر ما روی دستگاه موجود باشد و چنانچه موجود نبود، پیغامی با این مضمون را به کاربر نمایش داده و یا به صفحه دانلود مارکت هدایت شود.<br />
به عنوان یک مثال دیگر، شرکت ارائه دهنده تاکسی آنلاین را درنظر بگیرید که دارای دو اپلیکیشن اندرویدی است. یک برنامه مخصوص مسافران و دیگری ویژه‌ی رانندگان.<br />
قصد داریم در هردو برنامه گزینه‌ای برای انتقال به برنامه دیگر اضافه کنیم به طوری که اگر در اپ ویژه‌ی مسافر، روی گزینه موردنظر کلیک شد، اپ ویژه راننده اجرا شده و بلعکس. و البته اینکه چنانچه برنامه دوم روی دستگاه نصب نبود، پیغام متناسب با آن نمایش داده شده و یا کاربر به صفحه نصب آن در مارکت یا مرورگر هدایت شود.<br />
کاربردهای متعدد دیگری هم برای این قابلیت وجود دارد که در اینجا تنها به دو مورد اکتفا می‌کنم.<br />
در ادامه مبحث در قالب یک پروژه ساده، این قابلیت را بررسی و تمرین می‌کنیم.</p>
<h2 id="create-project-on-android-studio">ساخت پروژه بررسی نصب بودن برنامه اندرویدی</h2>
<p>طبق مبحث <a href="http://android-studio.ir/create-android-project-and-its-structure/" target="_blank" rel="noopener noreferrer">آموزش ساخت پروژه در اندروید استودیو</a> یک پروژه اندرویدی با نام App installed می‌سازم. اکتیویتی را از نوع Empty Activity و زبان را Java انتخاب کردم.<br />
ابتدا نحوه بررسی نصب بودن برنامه در اندروید را به دو روش متفاوت انجام و در مرحله آخر نیز فراخوانی مارکت و یا باز کردن لینک صفحه دانلود برنامه را بررسی می‌کنیم.</p>
<h3 id="using-getPackageInfo-method">روش اول: استفاده از متد getPackageInfo</h3>
<p>در روش نخست، با استفاده از متد getPackageInfo و دستور try catch در لیست برنامه‌های نصب شده روی دستگاه اندرویدی نام پکیج برنامه مدنظر را جستجو می‌کنیم. چنانچه نام پکیج یا همان Package name موردنظر موجود بود کدهای درون بلاک try و در غیر اینصورت قسمت catch اجرا خواهد شد:</p>
<pre class="brush: java; title: ; notranslate">
private boolean installedOrNot(Context cnt, String packageName) {

    PackageManager pm = cnt.getPackageManager();
    boolean appInstalled;

    try {
        pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
        appInstalled = true;
        Toast.makeText(cnt, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
    } catch (PackageManager.NameNotFoundException e) {
        appInstalled = false;
        Toast.makeText(cnt, &quot; برنامه قبلا نصب نشده است &quot;, Toast.LENGTH_LONG).show();
    }
    return appInstalled;

}
</pre>
<p>مطابق با کد فوق ابتدا یک متد از جنس boolean و با نام دلخواه installedOrNot درون اکتیویتی و بعد از متد onCreate تعریف کرده‌ام. این متد دو پارامتر دارد که اولی کانتکست و دومی از جنس رشته تعریف شده.<br />
درون این متد ابتدا یک شیء از کلاس PackageManager با نام دلخواه pm ساخته شده است. سپس یک متغیر از جنس boolean با نام appInstalled تعریف شده.<br />
داخل بلاک try و با استفاده از متد getPackageInfo نام پکیج بررسی می‌شود. چنانچه پکیج و به عبارتی اپلیکیشن مدنظر قبلا نصب شده باشد، متغیر appInstalled مقدار true گرفته و کدهای بعد از آن اجرا می‌شود که من در اینجا یک پیغام Toast تعریف کردم.<br />
برای بلاک catch یک Exception (استثناء) از نوع PackageManager.NameNotFoundException تعریف شده که از نامش پیداست مربوط به پیدا نشدن نام است (NameNotFound). یعنی هنگامی که نام پکیج یافت نشد این استثناء اجرا می‌شود که در اینصورت مقدار false برای متغیر appInstalled در نظر گرفته شده و مانند قسمت قبل یک پیغام برای کاربر ظاهر می‌شود که اعلام می‌کند برنامه مدنظر نصب نشده است.<br />
در نهایت از آنجایی که تابع از جنس boolean بود لازم است appInstalled را return کنیم.<br />
حالا متد را در onCreate تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
installedOrNot(getApplicationContext(), &quot;com.farsitel.bazaar&quot;);
</pre>
<p>پارامتر نخست مربوط به کانتکست است که قبلا آشنا شدیم و نیاز به توضیح مجدد نیست. برای پارامتر دوم نام پکیج موردنظرم را در قالب یک String وارد می‌کنم. من Package name مارکت ایرانی بازار را تعریف کرده‌ام. تا اینجای کار اکتیویتی ما به صورت زیر تکمیل شده است:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.appinstalled;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        installedOrNot(getApplicationContext(), &quot;com.farsitel.bazaar&quot;);

    }

    private boolean installedOrNot(Context cnt, String packageName) {

        PackageManager pm = cnt.getPackageManager();
        boolean appInstalled;

        try {
            pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
            appInstalled = true;
            Toast.makeText(cnt, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
        } catch (PackageManager.NameNotFoundException e) {
            appInstalled = false;
            Toast.makeText(cnt, &quot;برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
        }
        return appInstalled;

    }

}
</pre>
<p>خب! پروژه را اجرا و تست می‌کنیم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/appinstalled/app_not_installed_already_using_getPackageInfo_method.png" alt="بررسی نصب بودن یا نبودن یک اپلیکیشن در اندروید توسط getPackageInfo" /></p>
<p>به محض اجرای برنامه روی شبیه ساز، پیغام &#8220;برنامه قبلا نصب نشده است&#8221; مشاهده شد. یعنی اپلیکیشن بازار روی این دیوایس نصب نیست.<br />
حالا برنامه بازار را روی دستگاه نصب کرده و مجدد پروژه را اجرا می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/appinstalled/app_installed_already.png" alt="برنامه قبلا روی دستگاه اندرویدی نصب شده است" /></p>
<p>برخلاف قسمت قبل اینبار پیغام &#8220;برنامه قبلا نصب شده است&#8221; ظاهر شد. بنابراین کد ما به درستی عمل می‌کند.<br />
برای مثال پرداخت درون برنامه‌ای که در ابتدای مبحث عنوان شد، بجای Toast اول می‌توانیم عملیات مربوط به انتقال به مارکت و ثبت تراکنش مالی توسط کاربر را انجام دهیم. همچنین بجای Toast دوم هم می‌توان کاربر را به صفحه دانلود مارکت هدایت کرد یا اینکه در قالب همین پیغام اعلام شود لازم است مارکت x روی دستگاه نصب شود. این بخش را نیز در انتهای مبحث بررسی می‌کنیم.</p>
<h3 id="using-intent">روش دوم: استفاده از intent</h3>
<p>قبلا در جلسه <a href="http://android-studio.ir/intent/">آموزش intent در اندروید</a> با اینتنت‌ها آشنا شدیم. در این روش با استفاده از intent، نام پکیج کلیه برنامه‌های نصب شده روی دستگاه اندرویدی را در یک لیست ذخیره کرده و سپس پکیج موردنظر را درون آن لیست جستجو می‌کنیم.<br />
ابتدا کد مربوط به قسمت قبل را کامنت می‌کنم تا غیر فعال شود. سپس دو متد به صورت زیر به اکتیویتی اضافه می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
protected List&lt;String&gt; getInstalledAppsPackage() {

    Intent mIntent = new Intent(Intent.ACTION_MAIN, null);
    mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    List&lt;ResolveInfo&gt; resolveInfoList = getPackageManager().queryIntentActivities(mIntent, 0);
    List&lt;String&gt; packageNameList = new ArrayList&lt;&gt;();

    for (ResolveInfo resolveInfo: resolveInfoList) {
        ActivityInfo actInfo = resolveInfo.activityInfo;
        packageNameList.add(actInfo.applicationInfo.packageName);
    }

    return packageNameList;

}

private void appStatus() {

    final List&lt;String&gt; installedPackages = getInstalledAppsPackage();

    if (installedPackages.contains(mPackageName)) {
        Toast.makeText(this, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
    } else {
        Toast.makeText(this, &quot;برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
    }

}
</pre>
<p>ملاحظه می‌کنید ابتدا یک تابع از جنس List<String> و نام دلخواه getInstalledAppsPackage تعریف شده. درون این تابع، اکتیویتی لانچر هر یک از برنامه‌های نصب شده روی دستگاه توسط Intent.CATEGORY_LAUNCHER مشخص می‌شود. سپس Package name آنها توسط actInfo.applicationInfo.packageName موجود در حلقه for، دریافت و به لیست packageNameList اضافه (add) می‌گردد.<br />
در ادامه کار یک متد دیگر با نام دلخواه appStatus تعریف شده که درون آن بررسی می‌کنیم آیا این لیست شامل mPackageName ای که تعریف کرده‌ایم هست (contains) یا نه. چنانچه این شرط برقرار بود، پیغام نصب و در غیر اینصورت پیغام عدم نصب نمایش داده خواهد شد.<br />
در نهایت، متد appStatus را در onCreate فراخوانی می‌کنم.<br />
برای تست این کد، نام پکیج مربوط به پلیر VLC را در بدنه اکتیویتی تعریف می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private String mPackageName = &quot;org.videolan.vlc&quot;;
</pre>
<p>تا اینجای کار اکتیویتی به صورت زیر تکمیل شده است:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.appinstalled;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private String mPackageName = &quot;org.videolan.vlc&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // installedOrNot(getApplicationContext(), &quot;com.farsitel.bazaar&quot;);
        appStatus();

    }

    /* private boolean installedOrNot(Context cnt, String packageName) {

        PackageManager pm = cnt.getPackageManager();
        boolean appInstalled;

        try {
            pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
            appInstalled = true;
            Toast.makeText(cnt, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
        } catch (PackageManager.NameNotFoundException e) {
            appInstalled = false;
            Toast.makeText(cnt, &quot; برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
        }
        return appInstalled;

    } */


    protected List&lt;String&gt; getInstalledAppsPackage() {

        Intent mIntent = new Intent(Intent.ACTION_MAIN, null);
        mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        List&lt;ResolveInfo&gt; resolveInfoList = getPackageManager().queryIntentActivities(mIntent, 0);
        List&lt;String&gt; packageNameList = new ArrayList&lt;&gt;();

        for (ResolveInfo resolveInfo: resolveInfoList) {
            ActivityInfo actInfo = resolveInfo.activityInfo;
            packageNameList.add(actInfo.applicationInfo.packageName);
        }

        return packageNameList;

    }

    private void appStatus() {

        final List&lt;String&gt; installedPackages = getInstalledAppsPackage();

        if (installedPackages.contains(mPackageName)) {
            Toast.makeText(this, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(this, &quot;برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
        }

    }

}
</pre>
<p>با اجرای مجدد پروژه، پیغام عدم نصب نمایش داده خواهد شد زیرا این پلیر روی دستگاه نصب نیست.<br />
در قسمت بعد نحوه هدایت کاربر به صفحه نصب یا دریافت برنامه را بررسی می‌کنیم.</p>
<h3 id="open-app-store-or-a-web-page">هدایت کاربر به صفحه نصب برنامه در مارکت یا یک صفحه وب</h3>
<p>چنانچه قصد داشته باشیم برای راحتی کاربر، در صورت نصب نبودن برنامه مورد نیاز به سرعت او را به صفحه دریافت و نصب برنامه هدایت کنیم، با استفاده از یک دستور try catch این کار را انجام می‌دهیم. قصد دارم برای روش دوم که مربوط به برنامه VLC بود، کدی را بنویسم که در صورت نصب نبودن آن، ابتدا صفحه مربوط به آن در مارکت اندرویدی پیش فرض روی دستگاه باز شده و چنانچه مارکتی وجود نداشت، کاربر به یک آدرس اینترنتی هدایت شود.<br />
یک متد دیگر با نام installApp به اکتیویتی اضافه کرده و به صورت زیر تکمیل می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
private void installApp() {

    try {
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(&quot;market://details?id=&quot; + mPackageName)));
    } catch (android.content.ActivityNotFoundException ane) {
        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(&quot;https://play.google.com/store/apps/details?id=&quot; + mPackageName)));
    }

}
</pre>
<p>برای انتقال کاربر به صفحه معرفی یک برنامه در مارکت‌های اندرویدی، uri به صورت </p>
<p style="text-align: left; direction: ltr;">
market://details?id=PACKAGE_NAME
</p>
<p>تعریف می‌شود. برای قسمت دوم شرط هم من صفحه معرفی برنامه در پلی استور را قرار دادم. نحوه تعیین آدرس صفحه برنامه‌ها در پلی استور به اینصورت است:</p>
<p style="text-align: left; direction: ltr;">
https://play.google.com/store/apps/details?id=PACKAGE_NAME
</p>
<p>حالا متد را در قسمت دوم شرط مربوط به appStatus فراخوانی می‌کنم:</p>
<pre class="brush: java; title: ; notranslate">
if (installedPackages.contains(mPackageName)) {
    Toast.makeText(this, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
} else {
    Toast.makeText(this, &quot;برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
    installApp();
}
</pre>
<p>دوباره پروژه را اجرا می‌کنم. با توجه به نصب نبودن پلیر VLC روی این دیوایس، صفحه نصب آن در مارکت بازار باز شد:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/appinstalled/open_play_store_to_install_app.png" alt="هدایت کاربر به اپ استور برای نصب برنامه اندرویدی" /></p>
<p>فروشگاه پلی استور به صورت پیش فرض روی دیوایس‌های <a href="http://android-studio.ir/install-genymotion/" target="_blank" rel="noopener noreferrer">شبیه ساز اندرویدی Genymotion</a> نصب نیست و با توجه به اینکه در قسمت نخست آموزش، برنامه کافه بازار را نصب کرده بودیم، در اینجا صفحه نصب پلیر VLC در این مارکت باز شد.<br />
در قدم نهایی، مارکت کافه بازار را حذف و پروژه را مجدد اجرا می‌کنم:</p>
<p><img decoding="async" class="aligncenter img-responsive" src="http://android-studio.ir/wp-content/uploads/appinstalled/open_app_page_in_google_play_web.png" alt="باز کردن صفحه نصب برنامه در گوگل پلی" /></p>
<p>مشاهده می‌کنید صفحه معرفی و نصب برنامه در play.google.com در مرورگر دستگاه باز شد.<br />
کد نهایی اکتیویتی:</p>
<p><span class="filename">MainActivity.java</span></p>
<pre class="brush: java; title: ; notranslate">
package ir.android_studio.appinstalled;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private String mPackageName = &quot;org.videolan.vlc&quot;;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // installedOrNot(getApplicationContext(), &quot;com.farsitel.bazaar&quot;);
        appStatus();

    }

    /* private boolean installedOrNot(Context cnt, String packageName) {

        PackageManager pm = cnt.getPackageManager();
        boolean appInstalled;

        try {
            pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
            appInstalled = true;
            Toast.makeText(cnt, &quot; برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
        } catch (PackageManager.NameNotFoundException e) {
            appInstalled = false;
            Toast.makeText(cnt, &quot;برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
        }
        return appInstalled;

    } */

    protected List&lt;String&gt; getInstalledAppsPackage() {

        Intent mIntent = new Intent(Intent.ACTION_MAIN, null);
        mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        List&lt;ResolveInfo&gt; resolveInfoList = getPackageManager().queryIntentActivities(mIntent, 0);
        List&lt;String&gt; packageNameList = new ArrayList&lt;&gt;();

        for (ResolveInfo resolveInfo: resolveInfoList) {
            ActivityInfo actInfo = resolveInfo.activityInfo;
            packageNameList.add(actInfo.applicationInfo.packageName);
        }

        return packageNameList;

    }

    private void appStatus() {

        final List&lt;String&gt; installedPackages = getInstalledAppsPackage();

        if (installedPackages.contains(mPackageName)) {
            Toast.makeText(this, &quot;برنامه قبلا نصب شده است&quot;, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(this, &quot;برنامه قبلا نصب نشده است&quot;, Toast.LENGTH_LONG).show();
            installApp();
        }

    }

    private void installApp() {

        try {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(&quot;market://details?id=&quot; + mPackageName)));
        } catch (android.content.ActivityNotFoundException ane) {
            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(&quot;https://play.google.com/store/apps/details?id=&quot; + mPackageName)));
        }

    }

}
</pre>
<p>موفق و پیروز باشید.</p>
<h3>مطالعه‌ی بیشتر:</h3>
<p style="text-align: left; direction: ltr;">
<a href="https://developer.android.com/reference/android/content/pm/ResolveInfo" target="_blank" rel="noopener noreferrer">https://developer.android.com/reference/android/content/pm/ResolveInfo</a><br />
<a href="https://developer.android.com/reference/android/content/pm/PackageManager" target="_blank" rel="noopener noreferrer">https://developer.android.com/reference/android/content/pm/PackageManager</a><br />
<a href="https://developer.android.com/reference/android/content/Intent" target="_blank" rel="noopener noreferrer">https://developer.android.com/reference/android/content/Intent</a>
</p>
<p class="source">توجه : سورس پروژه درون پوشه Exercises قرار دارد</p>
<div class="alert dlbox">
<span class="title"><i class="titleicon fa fa-download fa-lg"></i>دانلود نسخه PDF این آموزش به همراه سورس پروژه</span><br />
<i class="dlicon fa fa-square fa-lg"></i> تعداد صفحات : ۱۴<br />
<i class="dlicon fa fa-square fa-lg"></i> حجم : ۱ مگابایت<br />
<i class="dlicon fa fa-square fa-lg"></i> قیمت : رایگان<br />
<a href="http://dl.android-studio.ir/courses/InstalledApps.zip" class="button green edd-submit">دانلود رایگان با حجم ۱ مگابایت</a> <a href="http://dl2.android-studio.ir/courses/InstalledApps.zip" class="button red edd-submit">لینک کمکی</a>
</div>
<p>نوشته <a href="https://android-studio.ir/check-if-an-android-application-is-installed-or-not/">بررسی نصب بودن برنامه در اندروید</a> اولین بار در <a href="https://android-studio.ir">اندروید استودیو</a> پدیدار شد.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://android-studio.ir/check-if-an-android-application-is-installed-or-not/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using Disk: Enhanced 
Lazy Loading (feed)

Served from: android-studio.ir @ Y-m-d H:i:s by W3 Total Cache
-->