کوروتین ها در اندروید روشی جدید برای مدیریت thread ها در پس زمینه هستند. اصولا زمانی از coroutine استفاده میشه که قراره کاری با مدت زمان طولانی در پس زمینه اجرا بشه مثل صدا زدن یک وب سرویس یا کار کردن با دیتابیس.
البته باید به این نکته اشاره کنم که coroutineها thread نیستند بلکه یک راه برای مدیریت job ها روی thread ها هستند. یعنی شما میتونید چندین coroutine رو تو یک thread اجرا کنید.
یکی از مزایای استفاده از coroutineها مدیریت راحت تر کارهای سریالی هست، به این معنی که به عنوان مثال اگر نیاز باشه از نتیجه ی یک وب سرویس به عنوان ورودی وب سرویس بعدی استفاده کنیم این کار با coroutineها خیلی ساده تر قابل انجام هستن.
در ادامه چندتا مثال ساده میزنیم و ارسال یک وب سرویس با استفاده از coroutine رو با هم میبینیم.
مرحله اول برای استفاده از coroutineها اضافه کردن dependence مورد نیازش به پروژمونه، برای این کار باید چند خط زیر رو به gradle اضافه کنید:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
کد زیر رو تو اکتیویتیمون قرار میدیم
class Coroutine : AppCompatActivity() { privateval RESULT_1 = "result #1" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_coroutine) buttom.setListener { CoroutineScope(IO).launch { fakeApiRequest() } } } private suspend fun fakeApiRequest(){ val result = getResult1FromApi() println("debug: $result") } private suspend fun getResult1FromApi():String{ logThread("getResult1FromApi") delay(1000) return RESULT_1 } private fun logThread(methodName : String){ println("debug: $methodName : ${Thread.currentThread().name}") } }
حالا ببینیم هر بخش این کد چیکار میکنه:
تابع fakeApiRequest همونجور که از اسمش پیداست قراره یه وب سرویس رو شبیه سازی کنه.
تابع getResult1FromApi نتیجه وب سرویس رو بعد از 1000 میلی ثانیه برامون ارسال میکنه.
تابع logThread هم قراره بهمون بگه که اسم thread ای که کد داره روش اجرا میشه چی هست.
نکته اول: چرا از کلید واژه suspend استفاده کردیم؟
هر وقت بخوایم یه تابع رو داخل یک coroutine صدا بزنیم باید اون تابع suspend باشه.
نکته دوم: تفاوت delay با Thread.sleep چیه؟
همونطور که بالاتر توضیح دادیم coroutineها threadهای جداگونه نیستن، وقتی از delay استفاده کنیم صرفا اون کوروتینی که داخلش delay گذاشتیم با تاخیر اجرا میشه اما thread.sleep باعث میشه که کل اون thread با تاخیر اجرا بشه. مثلا اگر 5 تا coroutine داخل اون thread قرار داشته باشه اون تاخیر روی همشون اعمال میشه.
نکته سوم: CoroutineScope(IO).launch:
وقتی میخوایم یه coroutine رو اجرا کنیم باید مشخص کنیم که میخوایم تو چه thread ای اجرا بشه. برای این کار 3 تا انتخاب داریم:
مورد اول IO: منظور input/output هست، وقتی از این scope استفاده میکنیم که بخوایم به عنوان مثال یک وب سرویس رو صدا بزنیم یا با دیتابیس کار کنیم.
مورد دوم Main: وقتی این مورد رو استفاده میکنیم که بخوایم با mainthread کار کنیم و نیاز باشه با ui در ارتباط باشیم.
مورد سوم Default: این مورد هم وقتی که قرار باشه یه پردازش خیلی سنگین اجرا بشه ازش استفاده میکنیم.
حالا اگر کد بالا رو اجرا کنیم خروجی زیر رو میبینیم:
I/System.out: debug: getResult1FromApi : DefaultDispatcher-worker-1
I/System.out: debug: result #1
که خط اول همونطور که قبلا اشاره شد اسم thread ای که کد در حال اجرا شدن روش هست رو نشون میده و خط دوم هم خروجی وب سرویسمونه.
حالا یکم کد بالا رو توسعه میدیم، هدفمون اینه که خروجی وب سرویسمون رو داخل یک textview نشون بدیم.برای اینکار تابع fakeApiRequest رو به شکل زیر تغییر میدیم:
private suspend fun fakeApiRequest(){ val result = getResult1FromApi() text.text = result println("debug: $result") }
خب حالا اگه کد رو همینجا اجرا کنیم میبینیم که برنامه crash میکنه اما چرا؟
چون ما داریم کد رو روی IO اجرا میکنیم و همونطور که بالا توضیح دادیم این مورد داره تو پس زمینه اجرا میشه و برای برقراری ارتباط با ui باید روی mainthread باشیم.
برای حل کردن این مشکل کد زیر رو استفاده میکنیم:
private fun setNewText(input: String){ val newText = text.text.toString() + "\n$input" text.text = newText } private suspend fun setTextOnMainThread(input: String){ withContext(Main){ setNewText(input) } } private suspend fun fakeApiRequest(){ val result = getResult1FromApi() setTextOnMainThread(result) println("debug: $result") }
تیکه کدی که مشکلمونو حل میکنه (withContext(Main هست. با استفاده از withContext میتونیم scope مونو تغییر بدیم. ما هم اینجا از io به main منتقل شدیم تا بتونیم با ui در ارتباط باشیم.
خب همونطور که ابتدای مقاله اشاره کردم یکی از مزایای coroutineها این بود که کارای سریالی رو میشه راحت تر انجام داد. حالا در ادامه میبینیم که چطور میشه این کار رو انجام داد.
private suspend fun getResult2FromApi():String{ logThread("getResult2FromApi") delay(1000) return RESULT_2 }
تابع بالا رو به کدمون اضافه میکنیم و تابع fakeApiRequest رو به شکل زیر تغییر میدیم:
private suspend fun fakeApiRequest(){ val result = getResult1FromApi() setTextOnMainThread(result) println("debug: $result") val result2 = getResult2FromApi() setTextOnMainThread(result2) }
حالا اگر کد رو اجرا کنیم میبینیم که اول result #1 چاپ میشه و بعد یک ثانیه result #2 چاپ میشه.
حالا ممکنه یه جا نیاز داشته باشیم که وب سرویس دوم از نتیجه ی وب سرویس اول استفاده کنه، فقط کافیه که تابع getResult2FromApi رو به (getResult2FromApi(input: String تغییر بدیم و به شکل زیر ازش استفاده کنیم:
private suspend fun fakeApiRequest(){ val result = getResult1FromApi() setTextOnMainThread(result) println("debug: $result") val result2 = getResult2FromApi(result) setTextOnMainThread(result2) }
اینجوری داریم نتیجه ی وب سرویس اول رو به وب سرویس دوم پاس میدیم.
البته باید اشاره کنم که این بخاطر اینه که جفت این توابع دارن تو یه coroutine صدا زده میشن.
خب تا اینجا یه دید مختصری به coroutineها داشتیم و چند تا مثال ساده رو بررسی کردیم.
امیدوارم این مقاله کمکتون کنه.
این نوشته بر اساس ویدیوی زیر نوشته شد: