AsyncTask چیست؟

یک task غیر همزمان (Asynchronous) به محاسباتی گفته می شود که در بک گراند انجام شود و نتایجش در Thread UI منتشر شود.

این کلاس این اجازه را به شما می دهد که عملیات بک گراندی را انجام دهید و نتایج آن را در نخِ UI منتشر کنید؛ بدونِ اینکه مجبور باشید با نخ ها یا Handler ها سر و کله بزنید.

AsyncTask به شکل یک کلاس کمکی حولِ Thread  و Handler طراحی شده است و به صورت مجزا یک چهارچوب Threading ارائه نمی کند.

طبق مستندات گوگل، به صورت ایده آل، AsyncTask باید برای عملیات کوتاه استفاده شود (حداکثر چند ثانیه). اگر بخواهید برای مدتی طولانی نخ ها را در جریان نگه دارید، توصیه می شود که از API هایی که داخل پکیج java.Util.concurent  ارائه شده استفاده کنید، چیزهایی مثل Executor؛ FutureTask؛ و ThreadPoolExecutor.

Asynctask  برای عملیات زمان بری که فقط یک بار به اجرا نیاز دارند و نمیتوانند در نخ UI انجام شوند طراحی شده است. مثلا دریافت و یا پردازش داده ها وقتی که یک دکمه انتخاب شد مثالِ خوبی از این عملکرد است.

AsyncTask با Service ها چه تفاوتی دارد؟

همین ابتدا بگویم، مبحث دیگری در اندروید وجود دارد به نام Service ها، آموزش ویدئویی این مبحث را می توانید در آپارات مشاهده کنید.

برای افرادی که اندک آشنایی با بحث سرویس ها دارند، ممکن است سوال پیش بیاید که تفاوت سرویس و AsyncTask در چه چیزی است.

در بعضی از حالت ها، پیاده سازی یک عمل، با هر دو روش Service و AsyncTask امکان پذیر است. با این حال، معمولا یکی؛ از دیگری بسته به نوع task؛ مناسب تر است.

AsyncTask ها برای به انجام رساندنِ کارهای زمان برِ یک بار رخ دهنده (یعنی کارهایی که فقط یک بار نیاز به اجرا دارند) که نمی توانند در نخِ UI اجرا شوند طراحی شده است. یک مثال متداول برای استفاده از این عملکرد، دریافت و یا پردازش اطلاعات پس از کلیک کردن روی یک دکمه است.

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

اگر نیاز داشته باشیم که به صورت مداوم کاری را در بکگراند انجام دهیم، انتخاب Service یک انتخاب درست است. مثال هایی از این می توانند پخش موزیک، و چک کردن برای داده های جدید به صورت مداوم می باشد.

سرویس ها لزوما خارج از نخِ UI اجرا نمی شوند (اگر برایشان یک نخ جداگانه تعریف نکنیم).

در اکثر مواقع؛ service ها برای وقتی هستند که می خواهید یک کد را، حتی اگر اکتیویتی نرم افزار شما باز نیست اجرا کنید. AsyncTask اما برای اجرای بسیار ساده ی  کدها خارج از نخِ UI طراحی شده است.

(توضیح بالا از سایت StackOverflow ترجمه شده است)

ساخت و استفاده از AsyncTask

باید از AsyncTask فرزند بسازیم تا بتوانیم از آن استفاده کنیم. این فرزند، حداقل یکی از متدهای موجود در AsyncTask را باید پیاده سازی (override) کند (تابع doInBackground )، البته اغلب اوقات نیاز است تا تابع دومی هم باز نویسی شود (تابع onPostExecute ).

مثالی از این فرزند سازی را در زیر می بینیم:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

وقتی ساخته شد، استفاده از این task در اکتیویتی بسیار ساده خواهد بود:

new DownloadFilesTask().execute(url1, url2, url3);

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

 AsyncTask<URL, Integer, Long>

ما سه نوع داده مشخص کرده ایم، به این نوع داده ها، پارامتر های داده های عمومی یا generic types گفته می شود.

این سه نوع داده، به ترتیب Params ؛ Progress ، و Result  نامیده می شود.

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

1- نوع پارامتری که قرار است Asynctask به عنوان ورودی تابع doInBackground خود بگیرد تا با استفاده از آن کار را به انجام رساند چه باید باشد.

2- نوع واحدی که حین انجام رساندنِ کار، به عنوان سیگنالِ پیشرفت کار باید به UI ارسال شود چیست؟ (مثلا برای اینکه نشان دهیم چند درصد از دانلود پیش رفته است، شاید رشته یا عدد صحیح انتخاب مناسبی باشد.)

3- داده ای که میخواهیم این AsyncTask به عنوان نتیجه برای ما ارسال کند از چه نوعی باید باشد؛ مثلا حجم فایل دانلود شده می تواند با نوع داده ای Long به عنوان نتیجه برایمان ارسال شود.

اگر یک یا چند تا از این سه نوع هم در مورد کاری ما استفاده نمی شوند، می توانیم در تعریف به جایشان از Void استفاده کنیم:

private class MyTask extends AsyncTask<Void, Void, Void> { ... }

مراحل اجرای AsyncTask

وقتی یک Asynchronous task اجرا می شود، اجرای task این 4 مرحله را طی می کند:

1- onPreExecute : این اولین تابعی است که در حین اجرای تسک اجرایی می شود، این تابع در نخِ UI اجرا شده و برای آماده سازی task استفاده می شود، مثلا فعال کردن نمایش یک ProgressBar در رابط کاربری می تواند مثال خوبی برای کاربرد آن باشد.

2- doInBackground : این تابع در نخِ background (نخی جدا از نخِ UI) و به محص تمام شدنِ اجرایِ onPreExecute  اجرا می شود.اینجا جایی است که محاسبات پس زمینه ای که میتواند مدت زمان زیادی نیز طول بکشد انجام می گیرد. نتیجه محاسبات در این مرحله باید برگشت داده شود که این داده return شده به مرحله آخر ارسال می شود.

این مرحله می تواند از publishProgress هم برای ارسال یک یا چند واحد progress استفاده کند. این مقادیر ارسال شده در نخِ UI منتشر شده و در تابع onProgressUpdate  دریافت می شود.

3- onProgressUpdate  این تابع در نخِ UI و بعد از اینکه تابع publishProgress  فراخوانی شد اجرا می شود. مدت زمان اجرایی شدنِ کامل Task معلوم نیست. به همین دلیل این تابع برای نشان دادن میزان پیشرفت کار در حال انجام در بک گراند به کاربر و در رابط کاربری تعبیه شده است. برای مثال این تابع می تواند برای نشان دادن میزان پیشرفت انجام یک عملیات توسط progressbar یا نمایش Log به کاربر استفاده شود.

4- onPostExecute : این تابع در نخِ UI و پس از اینکه محاسباتِ بک گراند (تابع doInBackground ) انجام شد اجرایی می شود. نتیجه برگشت داده شده توسط تابع doInBackground  به این تابع به عنوان ورودی ارسال می شود.

متوقف کردن اجرای AsyncTask

اینکار می تواند با فراخوانی تابع ;(cansel(boolean  انجام شود. فراخوانی این تابع باعث می شود تا تابع isCanceled  در فراخوانی اهی بعدی اش true  برگرداند.

بعد از فراخوانی تابع cancel، به جای اجرا شدنِ onPostExecute ، تابع onCanceled  بعد از return   شدن تابع از طرفِ doInBackgroud  فراخوانی می شود.

قوانین نخ ها

چند قانون در نخ بندی نرم افزار وجود دارد که برای عملکردِ مناسب در استفاده از این کلاس باید رعایت کنید:

  • کلاس AsyncTask حتما باید در نخِ UI بارگذاری یا load شود.
  • داده ی task (شیء task) باید حتما در نخِ UI تولید شود.
  • اجرایی کردنِ نخ با استفاده از تابع execute باید حتما در نخِ UI انجام شود.
  • توابع onPreExecute ، onPostExecute ، doInBackground  و onProgressUpdate  را نباید به صورت دستی فراخوانی کنید.
  • یک task فقط می تواند یک بار اجرا شود، تلاش دوباره برای اجرایی کردنِ یک task برای بار دوم موجب پرتاب یک exception می شود)

قابل رویت بودنِ حافظه ها در AsyncTask

Asynctask تضمین می کند همه فراخوانی ها، به صورتی همگام سازی شده که بدون نیاز به پیاده سازیِ یک روند همگام شده‌ی مجزا یا جدید؛ همه‌ی این عملیات به صورت امن و قطعی اجرا می‌شوند:

1- مقدار دهی متغیر ها در سازنده یا در تابع onPreExecute  و ارجاع به آنها در doInBackground

2- مقدار دهی متغیر ها در doInBackground  و استفاده از آنها در onProgressUpdate  و onPostExecute .

پروژه عملی نحوه کار با AsyncTask

به عنوان پروژه ای ساده برای درک مفهوم عملکردِ AsyncTask، برنامه ای برای دانلود یک فایل از طریق اینترنت ونمایش میزان پیشرفت دانلود را پیاده سازی می کنیم.

ویدئوی مربوط به این آموزش را می توانید در آپارات مشاهده کنید:

اکثر مطالب عنوان شده در این بخش از مستندات گوگل برداشته و ترجمه شده است.