thread یا نخ در اندروید

راهنمای کامل Thread یا نخ در برنامه نویسی اندروید

هر برنامه نویس روی هر پلتفرمی بالاخره باید با مفهوم Thread ها آشنا شود. آشنایی و تسلط به Thread ها در برنامه نویسی برای انجام عملیات های غیرهمزمان در اپلیکیشن کاملا ضروری است. وقتی یک اپلیکیشن در سیستم عامل اندروید اجرا میشود، اندروید یک پردازش Linux برای آن میسازد که یک Thread پردازشی روی آن وجود دارد. در حقیقت همه اپلیکیشن های اندروید دارای یک یا چند Thread هستند که میتوانند میزبان پردازش های مختلف باشند. در این مقاله از برنامه چی میخواهیم Thread ها در برنامه نویسی اندروید را بصورت کامل بررسی کنیم.

نخ

این نوشته یک هندبوک کامل درباره Thread ها در اندروید است. مطالبی مانند مفهوم نخ ها، نحوه کار با آنها، انوع مختلف نخ ها، کلاس های Thread در اندروید و مثال های مختلف صحبت کردیم. لیست تیتر ها در باکس زیر قرار دارند.


Thread یا نخ چیست؟

هر اپلیکیشن در سیستم عامل اندروید دارای یک Process (یا همون پردازش) مخصوص به خود میباشد. یعنی وقتی که اپلیکیشن (یا هر کامپوننت از اپلیکیشن) را اجرا میکنیم، اندروید یک پردازش Linux برای این نرم افزار درست میکند. همه کدها و فعالیت های اپلیکیشن ما روی این Process اجرا میشوند. این پردازش ها برای هر اپ کاملا خصوصی و مجزا هستند و هیچ اپلیکیشنی به پردازش اپلیکیشن دیگر دسترسی ندارد.

اما در سیستم عامل اندروید، هر نرم افزار میتواند پردازش مخصوص به خودش را به چند قسمت تقسیم کند. یعنی ماشین مجازی جاوا این اجازه را به همه اپلیکیشن ها میدهد. میتوانیم به هرکدام از زیر مجموعه های پردازش اصلی اپلیکیشن یک Thread در اندروید (یا همان نخ) بگوییم. با وجود این قابلیت، مفهوم MultiThreading و انجام کار ها بصورت همزمان (Sync)، غیر همزمان (Async)، در پس زمینه (Background) و غیره معنی پیدا میکنند.


انواع Thread در اندروید

انواع Thread در اندروید

در سیستم عامل اندروید، میتوانیم نخ ها را به دو دسته کلی تقسیم کنیم:

  1. Thread هایی که به اکتیویتی یا فرگمنت متصل میشوند: این نخ ها به چرخه عمر یک اکتیویتی یا فرگمنت متصل میشوند و به محض اینکه فرگمنت یا اکتیویتی از بین میرود، تخریب میشوند.
  2. Thread هایی که به هیچ اکتیویتی و فرگمنتی متصل نمیشوند: این نخ ها میتوانند بدون وابستگی به چرخه عمر اکتیویتی یا فرگمنتی که از داخل آنها اجرا شده اند، به حیات خودشان ادامه بدهند.

بعد از اشنایی با دسته بندی های کلی Thread ها در اندروید، باید بدانیم چه Thread هایی در هرکدام از این دسته ها قرار میگیرد. کامپوننت های Threading که به اکتیویتی یا فرگمنت متصل میشوند:

  • AsyncTask
  • Loaders

کامپوننت های Threading که به اکتیویتی یا فرگمنت متصل نمیشوند:

  • Service
  • Intent Service

برای هر دو دسته کامپوننت Threading بالا، پنج مدل نخ وجود دارد که در توسعه اپلیکیشن های موبایل اندروید به کار میروند:

  1. Main Thread
  2. UI Thread
  3. Worker Thread
  4. Any Thread
  5. Binder Thread

در ادامه میخواهیم همه این مدل ها را با هم بررسی کنیم.


1- نخ اصلی یا Main Thread

وقتی که یک اپلیکیشن در اندروید اجرا میشود، سیستم یک Thread برای آن میسازد که همه کامپوننت های اپلیکیشن بصورت پیشفرض روی آن اجرا میشوند. معمولا به این نخ، Main Thread گفته میشود. وظیفه اصلی Main Thread توزیع مناسب Event ها به اجزای رابط کاربری و همچنین برقراری ارتباط با کامپوننت های اپلیکیشن از طرف Android UI Toolkit است. یعنی نخ اصلی، به ازای همه اتفاقاتی که در رابط کاربری رخ میدهد Event مناسب را منتشر میکند و همچنین اگر کاربر گزینه ای انتخاب کرد که نیاز بود کامپوننت دیگری اجرا شود، این کار را انجام خواهد داد. دقت داشته باشید که همه کامپوننت های دیگر اپلیکیشن نیز، بصورت پیشفرض، روی Main Thread اجرا خواهند شد.

نخ اصلی یا Main Thread

 برای اینکه کاری کنید نرم افزار شما عملکرد خوبی داشته باشد و هیچوقت فریز نشود (همون هنگ کردن خودمون)، نباید هیچکدام از تسک های زمان بر، یا عملیات هایی که ممکن است با خطا روبرو شوند را روی Main Thread اجرا کنید.

ارسال درخواست برای شبکه، دریافت اطلاعات از دیتابیس یا بارگذاری کامپوننت های حجیم در اپلیکیشن، از کارهایی هستند که نباید آنها را روی Main Thread انجام بدهید. انجام دادن این تسک ها روی نخ اصلی به معنی انجام دادن آنها بصورت همزمان (Sync) است. وقتی که این اتفاق رخ بدهد، رابط کاربری اپلیکیشن تا زمانی که این تسک ها کامل شوند، بلاک میشود و به هیچکدام از فعالیت های کاربر پاسخی نشان نخواهد داد. به همین دلیل است که این موارد معمولا روی Thread های جداگانه ای قرار میگیرند تا بدون تاثیر روی رابط کاربری اپ، کارهای خودشان را انجام بدهند. به این کار، انجام تسک ها بصورت غیر همزمان (Async) گفته میشود.

هشدار: وقتی که رابط کاربری بخاطر انجام تسک های طولانی متوقف میشود، سیستم به کاربر یک اخطار با موضوع Application is Unresponsive نمایش میدهد که برای تجربه کاربری اتفاق بسیار بدی است.


2- نخ رابط کاربری یا UI Thread

نخ رابط کاربری Thread اصلی اجرایی برای اپلیکیشن های ماست. این Thread جایی است که اکثر کدهای اپلیکیشن ما در آن اجرا میشوند. همه کامپوننت های اپلیکیشن یعنی اکتیویتی ها، سرویس ها، ContentProvider ها و BroadcastReceiver ها درون این نخ ساخته شده و همه فراخوانی هایی که در سیستم برای این کامپوننت ها رخ میدهد در همین نخ اجرا میشوند.

برای مثال فرض کنید که اپلیکیشن شما فقط از یک اکتیویتی تنها درست شده است. در این حالت همه متد های چرخه عمر و بیشتر کدهای مدیریت Event های شما در UI Thread اجرا میشوند. یعنی متدهایی مانند onCreate، onPause، onDestroy، onClick و غیره. علاوه بر اینها همه به روز رسانی های UI در این نخ انجام میشوند. به عبارت دیگر هر اتفاقی که بخواهد رابط کاربری را به روز رسانی کند یا تغییر بدهد باید در این Thread رخ بدهد.

نخ رابط کاربری یا UI Thread

وقتی که خودمان به صورت صریح یک نخ جدید برای انجام تسک ها در پس زمینه درست میکنیم، کدها روی UI Thread اجرا نمیشوند. خب در این سناریو اگر تسک های پس زمینه نیاز داشته باشند که تغییراتی را روی UI اعمال کنند چطور باید به آن دسترسی داشته باشند؟

اینجاست که متد runOnUiThread به کار می آید. در حقیقت برای این کار شما باید از Handler استفاده کنید. این کلاس به نخ های پس زمینه اجازه میدهد بتوانند کدهایی را اجرا کنند که تغییراتی در UI به وجود بیاورد. برای اینکه بتوانید این کار را عملی کنید، باید کدهای تغییر دهنده رابط کاربری را درون یک آبجکت Runnable قرار بدهیم و این آبجکت را به متد runOnUiThread پاس بدهیم.

بیایید یک تعریف دیگر از UI Thread را با هم بررسی کنیم. نخ رابط کاربری به ما اجازه میدهد که تسک های خودمان را روی Thread پس زمینه انجام بدهیم و بعد نتیجه آنها را روی UI نمایش بدهیم.

هر اپلیکیشن دارای یک نخ مخصوص برای اجرای آبجکت های UI مانند آبجکت های View است. به این نخ، UI Thread گفته میشود. فقط آبجکت هایی که روی UI Thread اجرا میشوند به بقیه آبجکت هایی که روی همان نخ هستند دسترسی دارند. پس برای ارسال تسک ها از نخ های پس زمینه به UI Thread باید از یک آبجکت Handler استفاده کنیم که روی نخ رابط کاربری اجرا میشود.


3- نخ کارگر یا Worker Thread

نخ کارگر یا Worker Thread

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

در مثال زیر نحوه ساخت یک Worker Thread در اندروید را مشاهده میکنید:

Public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork(“http://example.com/image.png");
mImageView.setImageBitmap(b);}
}).start();
}

در مثال بالا، عمل دانلود کردن روی یک نخ، به غیر از UI Thread انجام میشود. اما قانون دوم استفاده از نخ ها در اندروید در این مثال نقض شده است. اگر دقت کنید میبینید imageView که یکی از اجزای UI Thread بود از درون همین نخ، که یک Worker Thread می باشد، دستکاری شده است.

بر اساس قانون دوم، اجزای رابط کاربری نباید از بیرون نخ رابط کاربری دستکاری شوند. راه حل این محدودیت، استفاده از متد runOnUiThread(Runnable) است. با بکار بردن این متد، میتوانید به UI Thread و Main Thread از نخ های دیگر دسترسی داشته باشید.

در نتیجه، کدهایی که در آبجکت Runnable که به این متد پاس داده میشود، در نهایت روی UI Thread اجرا میشود. دقت داشته باشید اگر نخ فعلی خودش در UI باشد، کدهای پاس داده شده بلافاصله اجرا میشوند. اما در غیر اینصورت اجرای کدها درون یک صف قرار میگیرند و در زمان مناسب اجرا خواهند شد.


4- Any Thread

Any Thread

یک Annotation در اندروید وجود دارد که میتواند کاری کند کدهای شما از همه نخ ها قابل دسترسی باشند. ساختار این Annotation به این صورت است:

@Target([AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.CLASS, AnnotationTarget.FILE,
AnnotationTarget.VALUE_PARAMETER]) class AnyThread

متدهایی که از این Annotaion استفاده کنند میتوانند از هر Thread دیگری فراخوانی بشوند (اصطلاحا به آنها Thread-Safe نیز گفته میشود). اگر یک کلاس را با این Annotation علامت گذاری کنیم، همه متد های آن کلاس میتوانند از هر نخ دیگری فراخوانی بشوند.

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

یک مثال از این روش را در زیر ببینید:

<code>
@AnyThread
public void deliverResult(D data) { ... }
</code>

5- Binder Thread

Binder Thread یک نخ جداگانه از سرویس شما را ارائه میدهد. Binder یک مکانیزم است که امکان ارتباطات بین پردازش ها (Inter-Process Communication) را فراهم میکند.

Binder Thread

برای درک بهتر این نخ ها بهتر است با مثال شروع کنیم. فرض کنید یک سرویس روی پردازش B دارید (تصویر را نگاه کنید). همچنین تعدادی اپلیکیشن دارید که با این سرویس B ارتباط دارند (مثلا یکی از این اپلیکیشن ها میتواند روی پردازش A باشد). بنابراین در این سناریو، سرویس B باید برای اپلیکیشن های مختلف بصورت همرمان نتیجه های متفاوت تولید کند. پس شما باید برای هر اپلیکیشن یک نسخه متفاوت از سرویس B اجرا کنید. سیستم عامل اندروید این نسخه های مختلف از سرویس B را روی نخ های متفاوتی اجرا میکند که به نام Binder Thread شناخته میشوند. (البته بهتر هست که درباره این مدل Thread ها بیشتر سرچ کنید).

binder thread

کلاس های Thread در اندروید

در مورد نخ اصلی اپلیکیشن و وظیفه های آن در قسمت های قبلی همین نوشته صحبت کردیم. هر بلوک کد که نیاز به اجرا داشته باشد، به یک صف کاری اضافه میشود و توسط نخ اصلی پردازش خواهد شد. از آنجایی که نباید از نخ UI برای کارهای سنگین استفاده کنیم، میخواهیم تعدادی از امکانات اندروید برای Multi Threading را با هم بررسی کنیم.

کلاس های Thread در اندروید

اندروید راه های زیادی برای ساخت و استفاده از نخ ها در اختیار ما قرار میدهد. همچنین کتابخانه های زیادی وجود دارند که مدیریت Thread ها را برای ما ساده تر میکنند. هر کدام از کلاس هایی که میخواهیم در این قسمت معرفی کنیم هدف خاصی دارند. اما شما باید با توجه به نیاز خودتان، بهترین آنها را انتخاب کنید. کلاس های مختلف Thread در اندروید:

  • کلاس Asynctask: برای انتقال کارها از نخ رابط کاربری به نخ های دیگر و برعکس.
  • کلاس HandlerThread: یک نخ برای Callback ها.
  • کلاس ThreadPoolExecutor: اجرای تعداد زیادی کار بصورت موازی.
  • کلاس IntentService: برای انتقال Intent ها به خارج از نخ رابط کاربری.

کلاس AsyncTask

کلاس AsyncTask استفاده از نخ رابط کاربری را برای ما ساده میکند. با استفاده از این کلاس میتوانید کارهای سنگین را به Thread های پس زمینه منتقل کنید و نتیجه آنها را روی UI Thread نمایش بدهید، بدون اینکه به Handler یا کار با Thread ها نیازی داشته باشید.

کلاس AsyncTask

کلاس AsyncTask برای این طراحی شده که یک کلاس دستیار برای کلاس Thread و Handler باشد و همینطور تبدیل به یک فرم ورک برای کار با نخ ها نشود. این کلاس باید برای عملیات های کوتاه به کار برده شود (نهایتا چند ثانیه). اگر میخواهید نخ هایی داشته باشید که عملیات های طولانی را انجام بدهند، پیشنهاد میکنم از API هایی که توسط پکیج java.util.concurrent ارائه میشوند استفاده کنید (مثلا Executor، ThreadPoolExecutor و FutureTask).

وقتی که یک تسک بصورت غیر همزمان اجرا میشود، چهار مرحله زیر برای آن اتفاق می افتد (هرکدوم این مرحله ها یه متد هستن که مثل چرخه عمر میتونین اونها رو مدیریت کنین):

  • مرحله onPreExecute: این متد قبل از این که تسک ها اجرا شوند، روی UI Thread فراخوانی میشود. در این مرحله باید کارهایی که میخواهید قبل از اجرای کدها انجام شوند را قرار بدهید. مثلا نشان دادن یک دیالوگ برای نمایش پیشرفت کار باید در این قسمت انجام شود.
  • مرحله doInBackground(params): این متد روی نخ پس زمینه فراخوانی میشود، دقیقا بعد از اینکه اجرای  onPreExecute تمام شد. از این مرحله برای انجام محاسباتی که زمان زیادی نیاز دارند، در پس زمینه استفاده میشود. پارامترهای مورد نیاز برای تسک های غیر همزمان، به این متد پاس داده میشوند. نتیجه عملیات هایی که در این مرحله انجام میشوند، در onPostExecute به دست ما خواهند رسید. همچنین اینجا میتوانید متد publishprogress را برای منتشر کردن مقدار پیشرفت به کار ببرید.
  • مرحله onProgressUpdate(progress): این متد روی UI Thread فراخوانی میشود، بعد از اینکه متد publishprogress صدا زده شود. از این متد برای نمایش پیشرفت روی رابط کاربری استفاده میشود، در حالیکه عملیات در پس زمینه همچنان در حال انجام است. به عنوان مثال میتوانید یک Progress Bar روی صفحه نشان بدهید که کاربر از پیشرفت تسک ها با خبر باشد.
  • مرحله onPostExecute(Result): وقتی که همه محاسبات در پس زمینه تمام شوند، این متد روی نخ رابط کاربری فراخوانی میشود. نتیجه انجام تسک ها در نخ های پس زمینه، به عنوان یک پارامتر به این متد پاس داده میشود.

دقت داشته باشید یک تسک در هر زمانی میتواند با صدا زدن متد cancel(boolean) متوقف شود. قبل از انجام این کار باید بررسی کنیم که آیا تسک قبلا متوقف شده یا هنوز در حال اجرا است؟

پیاده سازی

private class AsyncTaskRunner extends AsyncTask<String, String, String> {
@Override  protected void onPreExecute() {
progressDialog.show();
}
@Override  protected String doInBackground(String… params) {          . doSomething();
publishProgress("Sleeping…"); // Calls onProgressUpdate()
return resp;
}
@Override   protected void onPostExecute(String result) {
// execution of result of Long time consuming operation            . progressDialog.dismiss();
updateUIWithResult() ;
}
@Override  protected void onProgressUpdate(String… text) {
updateProgressUI();
}
}

چه زمانی باید از AsyncTask استفاده کنیم؟

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


کلاس HandlerThread

کلاس HandlerThread

کلاس HandlerThread یک زیر کلاس از کلاس Thread معمولی جاوا است. یک نخ Handler مدت زمان زیادی زنده میماند و تسک ها را از یک صف به نوبت برداشته و اجرا میکند. این کلاس ترکیبی از کلاس های دیگر اندروید است که اینها هستند:

  1. کلاس Looper: مسئول زنده نگه داشتن نخ و مدیریت صف تسک ها.
  2. کلاس MessageQueue: این کلاس پیام هایی که باید توسط Looper منتشر شوند را نگه داری میکند.
  3. کلاس Handler: این کلاس به ما اجازه میدهد آبجکت های Message که در ارتباط با MessageQueue همین نخ هستند را ارسال و پردازش کنیم.

پس ما میتوانیم این کلاس را در پس زمینه زنده نگه داریم و بصورت مرتب برای آن کارهایی را بفرستیم تا انجام بدهد. این روند تا زمانی که از کلاس خارج نشویم ادامه خواهد داشت. کلاس HandlerThread بیرون از چرخه عمر اکتیویتی ما اجرا میشود، بنابراین باید وقتی کار آن تمام میشود، به درستی آن را ببندیم وگرنه ممکن است مشکل Memory Leak در اپلیکیشن ما رخ بدهد.

دو راه اصلی برای ساخت HandlerThread وجود دارد:

  1. روش اول: یک آبجکت از کلاس HandlerThread بسازید و Looper را با استفاده از متد getLooper از کلاس HandlerThread دریافت کنید. بعد از این کار، یک آبجکت از کلاس Handler بسازید و آبجکت Looper را به آن پاس بدهید. الان میتوانید همه تسک های خودتان را برای این آبجکت از Handler پست کنید.
  2. روش دوم: یک کلاس CustomHandlerThread بسازید و کلاس HandlerThread را درون آن extend کنید. بعد یک آبجکت از کلاس Handler بسازید که تسک ها را پردازش کند. وقتی باید از این روش استفاده کنید که تسک های شما از قبل مشخص هستند و فقط نیاز دارید پارامتر ها را برای آنها بفرستید. یک مثال میتواند ساخت یک HandlerThread شخصی سازی شده برای دانلود موزیک یا تصاویر از شبکه باشد.
HandlerThread handlerThread = new HandlerThread("TesHandlerThread");
handlerThread.start();
Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper);
handler.post(new Runnable(){…});

وقتی که یک نخ Handler میسازید، فراموش نکنید که اولویت آن را برای سیستم مشخص کنید. زیرا سیستم میتواند تعداد محدودی از پردازش ها را بصورت همزمان انجام بدهد. مشخص کردن اولویت به سیستم کمک میکند که با روش بهتری تسک های شما را مدیریت کند، در حالیکه کارهای کم اهمیت تر در حال تلاش برای اجرا شدن با اولویت های پایین تر هستند.

کلاس HandlerThread

نکته: وقتی که کارهای شما تمام شد باید handlerThread.quit() را صدا بزنید که مشکل نشت مموری برای اپلیکیشن رخ ندهد. معمولا این کار را در متد onDestroy اکتیویتی انجام میدهند.

میتوانیم به روز رسانی های مربوط به رابط کاربری را با استفاده از Broadcast های محلی (Local Broadcasts) برای نخ رابط کاربری ارسال کنیم. برای این کار میتوانیم یک آبجکت HandlerThread با استفاده از mainLooper بسازیم. کدهای زیر را مشاهده کنید:

Handler mainHandler = new Handler(context.getMainLooper());

mainHandler.post(myRunnable);

چه زمانی باید از HandlerThread استفاده کنیم؟

نخ های Handler برای تسک های طولانی مدت در پس زمینه که نیاز زیادی به آپدیت رابط کاربری ندارند بسیار مناسب هستند.


کلاس ThreadPoolExecutor

یک استخر نخ (Thread Pool) خیلی ساده مانند یک استخر پر از Thread هاست که منتظرند تا برای یک تسک استفاده شوند. تسک هایی که به این نخ ها داده میشوند بصورت موازی اجرا خواهند شد. وقتی که کدها بصورت موازی اجرا میشوند، ما باید مطمئن شویم کدهای ما Thread-Safe هستند. یک Thread Pool معمولا دو مشکل زیر را حل میکند:

  • وقتی که تعداد زیادی تسک غیر همزمان داشته باشیم، زمان اجرای آن ها را کاهش میدهد.
  • وسیله ای برای محدود کردن و مدیریت منابع (شامل نخ ها) است وقتی که مجموعه ای از تسک ها قرار است انجام شوند.

بیایید با یک مثال این روند را بررسی کنیم. فرض کنید 40 فایل BMP (یا همون Bitmap) داریم که کدگشایی هرکدام از آنها 4 میلی ثانیه طول میکشد. اگر همه این فایل ها را روی یک نخ تنها کدگشایی کنیم، 160 میلی ثانیه طول میکشد. اما اگر همین کار را با استفاده از 10 نخ انجام بدهیم که هر کدام 4 فایل را کدگشایی کنند، کل پردازش ما فقط 16 میلی ثانیه طول خواهد کشید.

کلاس ThreadPoolExecutor

مشکل اصلی این سناریو این است که چطور باید کار را بین Thread ها تقسیم کنیم، چطور کارها را برنامه ریزی کنیم و چطور نخ ها را مدیریت کنیم؟ این مسئله واقعا مشکل بزرگی است. اینجا جایی است که کلاس ThreadPoolExecutor میتواند به ما کمک کند.

ThreadPoolExecutor یک کلاس است که AbstractExecutorService را extend میکند. کلاس ThreadPoolExecutor همه نخ ها را مدیریت میکند.

  • این کلاس تسک های مختلف را به Thread ها میدهد.
  • این کلاس نخ ها را زنده نگه میدارد.
  • این کلاس نخ ها را بعد از اتمام کار از بین میبرد.

روش کار این کلاس به اینصورت است که همه تسک ها را درون یک صف کاری نگهداری میکند. هروقت که یکی از نخ های موجود در Thread Pool خالی میشود، این کلاس یکی از تسک های موجود در صف کاری را به آن نخ اختصاص میدهد.

Runnable

Runnable یک اینترفیس است. کلاس هایی که قرار است آبجکت های آنها روی یک نخ دیگر اجرا شوند، باید این اینترفیس را Implement کنند. به زبان ساده: Runnable یک دستور یا یک تکه کد است که میتواند اجرا شود. از این اینترفیس برای اجرای دستورات روی یک Thread دیگر استفاده میشود.

Runnable mRunnable = new Runnable() {
@Override
public void run() {
// Do some work
}
};

Executor

Executor یک اینترفیس است که تسک ها را اجرا میکند. یعنی همه کدهایی که در Runnable قرار دارند توسط این اینترفیس اجرا خواهند شد.

Executor mExecutor = Executors.newSingleThreadExecutor(); mExecutor.execute(mRunnable);

ExecutorService

ExecutorService یک Execcutor است که تسک های غیرهمزمان را مدیریت میکند.

ExecutorService mExecutorService = Executors.newFixedThreadPool(10);
mExecutorService.execute(mRunnable);

ThreadPoolExecutor

ThreadPoolExecutor یک ExecutorService است که اجرای تسک ها را به عهده یک استخر نخ میگذارد.

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

کلاس ThreadPoolExecutor

وقتی که میخواهیم یک آبجکت از ThreadPoolExecutor بسازیم، میتوانیم تعداد نخ های اولیه و ماکزیمم تعداد این نخ ها را مشخص کنیم. با این کار وقتی که Thread های بیشتری در دسترس بود، سیستم بصورت اتوماتیک تعداد نخ های مورد نیاز ما را در اختیارمان قرار خواهد داد.

معمولا پیشنهاد میشود تعداد Thread ها را بر اساس تعداد هسته های پردازشگر تنظیم کنید. میتوانید تعداد هسته ها را از این طریق دریافت کنید:

int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

نکته: این روش همیشه تعداد درست هسته هایی که بصورت فیزیکی در CPU قرار دارند را بر نمیگرداند. چون ممکن است سیستم تعدادی از هسته ها را خاموش کرده باشد.

ThreadPoolExecutor(
int corePoolSize,    // Initial pool size
int maximumPoolSize, // Max pool size
long keepAliveTime,  // Time idle thread waits before terminating
TimeUnit unit        // Sets the Time Unit for keepAliveTime
BlockingQueue<Runnable> workQueue)  // Work Queue

اما این پارامتر ها چه چیزی را نشان میدهند؟

  1. corePoolSize: کمترین تعداد نخ هایی که درون استخر نگهداری میشوند. در ابتدای کار، تعداد صفر عدد Thread در استخر وجود دارد. اما وقتی تسک ها به صف اضافه میشوند، نخ های جدید به وجود می آیند. اگر تعداد Thread های در حال اجرا، کمتر از مقدار این پارامتر باشد، Executor همیشه به جای اضافه کردن تسک به صف، نخ جدید ایجاد میکند.
  2. MaximumPoolSize: بیشترین مقدار Thread هایی که در یک استخر مجاز است داشته باشیم. اگر مقدار این پارامتر بیشتر از corePoolSize باشد و تعداد نخ های فعلی بیشتر یا مساوی corePoolSize باشد، تنها زمانی که صف پر شده باشد، یک نخ کارگر جدید ساخته خواهد شد.
  3. keepAliveTime: وقتی که تعداد Thread ها بیشتر از تعداد هسته ها باشد، نخ هایی که بدون هسته هستند (نخ های اضافی بدون کار) منتظر تسک های جدید میشوند. اما اگر در مدت زمانی که توسط این پارامتر تعیین میشود، کاری به آنها نرسد از بین میروند.
  4. unit: واحد زمانی برای پارامتر keepAliveTime.
  5. workQueue: صف تسک ها، که فقط تسک های قابل اجرا را نگهداری میکند.

چه زمانی باید از ThreadPoolExecutor استفاده کنیم؟

در حقیقت ThreadPoolExecutor یک فرم ورک قدرتمند برای اجرای تسک هاست. ما میتوانیم از این فرم ورک برای اجرای تعداد زیادی از تسک ها بصورت موازی استفاده کنیم. این کلاس از اضافه کردن تسک به صف، کنسل کردن تسک ها و اولویت دهی به آنها پشتیبانی میکند.


کلاس IntentService

کلاس IntentService یک زیر کلاس است که از کلاس Service ارث بری میکند. پس برای اینکه IntentService را بشناسیم، باید اول با کلاس Service آشنا باشیم.

Service یکی از کامپوننت های بسیار مهم در برنامه نویسی اندروید است. گاهی اوقات ما نیاز داریم حتی بعد از بسته شدن اپلیکیشن تعدادی از کارها را انجام بدهیم. در این سناریو سرویس ها میتوانند کمک کننده باشند. سرویس ها میتوانند با استفاده از متدهای startService و stopService شروع شده یا تخریب شوند و میتوانند در پس زمینه به مدت طولانی زنده بمانند. همچنین میتوانید با صدا زدن متد stopSelf درون خود سرویس، آن را متوقف کنید.

کلاس IntentService

تعدادی از متدهای چرخه عمر که برای انجام پردازش های مختلف مفید هستند را با هم بررسی میکنیم:

  • متد onCreate: در طول عمر سرویس، فقط یکبار صدا زده میشود.
  • متد onStartCommand: این متد برای اولین بار بعد از onCreate صدا زده میشود. اما وقتی هرکدام از کامپوننت ها متد startService را صدا بزنند، این متد بصورت مستقیم فراخوانی میشود.
  • متد onDestroy: این متد وقتی که سرویس را متوقف کنیم صدا زده خواهد شد.

پس مسیر طبیعی سرویس به این شکل خواهد بود:

onCreate() -> onStartCommand() -> onDestroy()

خب الان باید به بحث IntentService برگردیم. این سرویس دقیقا شبیه بقیه سرویس ها استارت میشود (یعنی با صدا زدن متد startService روی نخ اصلی). IntentService همه Intent ها را درون onHandleIntent مدیریت میکند (برخلاف سرویس معمولی که این کار را در onStartCommand انجام میدهد). این کلاس روی Worker Thread اجرا میشود و وقتی که تسک های آن تمام شود بصورت خودکار متوقف خواهد شد. برای استفاده از آن باید کلاس IntentService را extend کرده و متد onHandleIntent را Implement کنید.

نکته: IntentService روی یک نخ کارگر تکی اجرا میشود. اما سرویس روی نخ اصلی اجرا میشود. پس فقط هربار فقط یک تسک اجرا میشود.

IntentService همه محدودیت های کار در پس زمینه که بعد از اندروید 8.0 (API 26) به بعد اضافه شد را دارد. برای بیشتر سناریو ها، شاید بهتر باشد که از JobIntentService استفاده کنید که به جای سرویس، از Job استفاده میکند (البته اگر روی اندروید 8.0 به بعد اجرا شود).

چه زمانی باید از IntentService استفاده کنیم؟

IntentService درخواست ها را به صورت غیر همزمان مدیریت میکند. اگر نمیخواهید سرویس های اپلیکیشن شما بصورت همزمان درخواست ها را مدیریت کنند، استفاده از این کلاس یکی از بهترین گزینه هاست.


استفاده از RxJava را فراموش نکنید

حتما شما هم درباره RxJava مطالبی شنیده اید. یک لایبرری که توسط Netfilx توسعه داده شده و دستیار بسیار خوبی برای برنامه نویسی است. در این مقاله از بلاگ برنامه چی میتوانید اطلاعات کاملی درباره آن به دست بیاورید:

همانطور که در مقاله اختصاصی برنامه نویسی Reactive توضیح داده شده، RxJava دو کامپوننت دارد: Observable و Observer. با استفاده از کتابخانه RxJava شما میتوانید نخی که میخواهید Observable در آن اجرا شود، و نخی که قرار است پاسخ را در آن دریافت کنید را مدیریت نمایید.

پس بدون پرداختن به دیگر ویژگی های این کتابخانه، RxJava به ما کمک میکند بدون درگیر شدن با پیچیدگی های مدیریت Thread در اندروید، بتوانید کدهای خودتان را به راحتی در پس زمینه اجرا کنید.


سوالی دارید؟

سوالی دارید؟

در این مقاله سعی کردیم همه مفاهیم و مواردی که برای کار با Thread ها در اندروید نیاز داشتید را بررسی کنیم. اما اگر هنوز سوالی در ذهن شما وجود دارد میتوانید آن را در قسمت نظرات (همین پایین) بنویسید تا به سرعت به آنها پاسخ بدهیم. البته اگر نکته های بیشتری بلد هستید، با نوشتن آن در قسمت کامنت ها حتما این مقاله آموزشی را کامل تر کنید.


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

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

  1. بازتاب: سرویس یا Service در اندروید - توضیح کامل + پروژه | برنامه چی | Barnamechi

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

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

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