انشاء نمط الـ AsyncTask

Mohammad Laifمنذ 5 سنوات

بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته

 

في الدروس السابقة رأينا كيفية انشاء واستخدام الـ Executors بامثله متعددة. في هذا الدرس سنرى طريقة عمل AsyncTask فهي تعتبر اسهل واسرع طريقه, وللعلم فنمط الـ AsyncTask مبني على نمط الـ ThreadPool مؤخراً!. نمط الـ AsyncTask لايحتاج الى تعريف فهو مشهور لدى جميع المبرمجين, وفي هذا الدرس سنقوم بتوضحيه بشكل مبسط وعميق. فمايميز هذا النمط هو سهولته بحيث انه يقوم باجراء المهام في خيط حاسوبي مختلف عن الخيط الرئيسي ثم يقوم بجلب النتائج الى الخيط الحاسوبي الرئيسي.

 

دائماً فضل استخدام AsyncTask على نمط Thread / Looper / Handler ونمط HandlerThreads في اجراء المهام البسيطة. ولكن في حالة التعامل مع قواعد البيانات او الشبكة العنكبوتيه فمن الافضل استخدام الـ Executor (اي نمط الـ Thread Pools).

 


عيوب الـ AsyncTask

بالرغم من انه نمط مشهور وفي كل مره تقوم قوقل بتحديثه وعدم اهماله (ليس كنمط الـ Loader التي تخلت depreciated عنه قوقل الان) واضافة له المزيد من الخصائص كتشغيلة في Executor (بإستخدام الدالة executeOnExecutor اثناء تشغيله بدلاً من الدالة execute) الا انه لايخلو من العيوب وابرزها:

  • تستخدم فقط للمهام القصيرة والبسيطة.
  • غير مدركة بالـ LifeCycle. يسبب خلل اثناء عمل الـ AsyncTask عندما تقوم بتدوير الشاشة او عند اغلاق وفتح التطبيق من جديد.
  • يتم تشغيلها بشكل تسلسلي. اي اذا قمت بتشغيل اثنتين فأن الثانيه لن تعمل الا عندما تنتهي الاولى من العمل. لتشغيلهم بشكل موازي عليك استخدام executeOnExecutor.
  • امر شائع ان يحدث Memory Leaks عند استخدام AsyncTask.

ومع كل هذه العيوب يبقى كحل قوي وسريع ومحبب لدى الجميع.

 

من الصورة نستطيع القول ان هذا النمط يحتوي على دوال متعددة تعمل على الخيط الحاسوبي الرئيسي لإستلام البيانات وتسليم النتيجة وايضاً دوال تعمل على خيط اخر في الخلفيه لإتمام العمل. نستطيع ايضاً ان نرى دورة حياة الـ AsyncTask فهي تبدأ من الدالة onPreExecute على الخيط الرئيسي ثم تنتقل الى الدالة doInbackground على الخيط الخلفي (وهنا نستطيع نداء الدالة publishProgress لتحديث نسبة اتمام العملية) ثم تنتقل الى الدالة onPostExecute على الخيط الرئيسي لنشر النتائج.

 

دوال نمط الـ AsyncTask
يتكون من اربع دوال وهي بالترتيب:

  • onPreExecute() ماقبل التنفيد, وتعمل على الخيط الحاسوبي الرئيسي.
  • doInBackground() يتم التنفيذ في خيط حاسوبي اخر في الخلفيه. ايضاً توجد دالة اخرى معاها بأسم publishProgress نستطيع استخدامها لإرسال التحديث الى الخيط الرئيسي.
  • onPregressUpdate() تستخدم لتحديث التقدم في اتمام المهمه وذلك من خلال استقبال التقدم من دالة الـ publishProgress, وتعمل على الخيط الرئيسي.
  • onPostExecute() لإستلام النتائج, وتعمل على الخيط الحاسوبي الرئيسي.

 

امثله برمجيه:

مدخلات ومخرجات كلاس AsyncTask
في كلاس الـ AsyncTask يوجد ثلاثة انواع للمدخلات والمخرجات نقوم بتحديد انواعهم باستخدام Generices كالتالي:

public class CastomAsyncTask extends AsyncTask<Params, Progress, Result> {}
  • الـ Params يمثل المدخلات التي نريد ادخالها الى هذه الكلاس للعمل عليها في الخلفية بخيط حاسوبي مختلف.
  • الـ Progress يمثل التقدم في مهمتنا.
  • الـ Result المخرجات او النتائج.

 

مثال:

لو اردنا ان نعمل كلاس AsyncTask لحساب عدد الحروف في عنصر String, نحتاج اولاً الى تحديد نوع المدخل String, وبالنسبه الى التقدم Progress فسنجلعه Integer, اما المخرج Result فسيكون من نوع Long, لتصبح الكلاس هكذا:

public class MyAsyncTask extends AsyncTask<String, Integer, Long> {}

 

دوال الـ  AsyncTask

هذه هي الدوال الضرورية لعمل كلاس الـ AsyncTask:

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * Runs on Background Thread
     */
    @Override
    protected Long doInBackground(String... strings) {
        return null;
    }

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onPostExecute(Long aLong) {
        super.onPostExecute(aLong);
    }

 

العلاقة بين المدخلات والمخرجات والدوال لكلاس الـ AsyncTask

لتفهم اكثر للعلاقات بين المدخلات والمخرجات والدوال انظر الصورة التالية:

  • المدخل Params وهو الـ String في مثالنا, سيذهب الى الدالة doInBackground.​​
  • التقدم Progress وهو الـ Integer في مثالنا, سيذهب الى الدالة onProgressUpdate.
  • المخرج Result وهو الـ Long في مثالنا, فسيذهب الى الدالة onPostExecute بعد ان تنتهي معالجته في الدالة doInBackground.

 

مايهمنا هو الداله doInBackground ففيها سنضع الشفرة التي نريد تشغيلها على خيط حاسوبي مختلف. وايضاً الداله onPostExecute التي سوف نستقبل النتيجه بداخلها.

 

سير البيانات في الـ AsyncTask

لفهم اكثر عن طريقة سير البيانات في هذا النمط انظر الصورة التالية:

ينتقل المدخل الى الدالة doInBackground ليعالج في خيط حاسوبي خلفي (وفي هذه الاثناء نستطيع نداء الدالة publishProgress ونضع لها قيمة التقدم في العملية ثم تقوم هي بدورها بنقله الى الدالة onProgressUpdate على الخيط الحاسوبي الرئيسي حتى نستلم التقدم في العملية) ثم بعد ذلك يتحول الى ناتج بعد معالجته وينتقل الى الدالة onPostExecute على الخيط الحاسوبي الرئيسي ونستطيع عرض الناتج (في هذا المثال قمنا بإستخدام نمط الـ Listener لنقل النتيجه وعرضها في الـ Activity).


انشاء كلاس AsyncTask
والان بعد ان عرفنا مكونات الـ AsyncTask هيا بنا ننشئ واحدة خاصة لحساب عدد الاحرف في متغير ما. لانشاء هذا النوع من الكلاس يجب علينا ان نعمل extends للكلاس الاصل AsyncTask (لإنها كلاس مجردة Abstract Class فيجب عمل وراثه لها اذا اردنا ان نستخدمها) ثم نقوم بتحديد الـ Generics الذي نريده.

الخطوات

انشاء الكلاس وجعلها تعمل extends للـ AsyncTask وتحديد المخرجات والمدخلات بالـ Generics:

public class MyAsyncTask extends AsyncTask<String, Integer, Long> {}

 

عمل Override لدوالها الضرورية:

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }


    @Override
    protected Long doInBackground(String... strings) {
        String name = strings[0];
        Long result = 0L;

        for (int i = 0; i < name.length(); i++) {
            result = result + 1;
            publishProgress(i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Long aLong) {
        super.onPostExecute(aLong);
    }

 

طريقة نقل البيانات من الـ AsyncTask الى الـ Activity

​​​​​الكثير يحب انشاء كلاس الـ AsyncTask كـ Inner Class في الـ Activity حتى يستطيع الوصول للحقول وعناصر الواجهه لتحديثها, ولكن هذا نمط غير جيد. بل الافضل هو انشاء كلاس خاص لها, وطريقة نقل المعلومات فيها باستخدام نمط المراقب او المتنصت. كالتالي:

انشاء واجهة interface تحتوي على الدوال التي من خلالها سوف نقوم بحفظ البيانات وارسالها من الـ AsyncTask الى الـ Activity:

/**
 * Created by MohammadL on 09/1/2019
 * Contact me at [email protected]
 */
public interface MyAsyncTaskCallbacks {
    public void onTriggeredProgressUpdate(Integer values);
    public void onFinishPostExecute(Long result);
}

 

عمل حقل لهذه الواجهه في كلاس الـ AsyncTask:

private MyAsyncTaskCallbacks mMyAsyncTaskCallbacks;

 

عمل له init في الـ Constructor في كلاس الـ AsyncTask:

    public MyAsyncTask(MyAsyncTaskCallbacks myAsyncTaskCallbacks) {
        mMyAsyncTaskCallbacks = myAsyncTaskCallbacks;
    }

 

وضع البيانات الناتجه في الدوال onProgressUpdate و onPostExecute المراد نقلها بداخل هذا الحقل في كلاس الـ AsyncTask:

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        if (mMyAsyncTaskCallbacks != null) {
            mMyAsyncTaskCallbacks.onTriggeredProgressUpdate(values[0]);
        }
        super.onProgressUpdate(values);
    }

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onPostExecute(Long aLong) {
        if (mMyAsyncTaskCallbacks != null) {
            mMyAsyncTaskCallbacks.onFinishPostExecute(aLong);
        }
        super.onPostExecute(aLong);
    }

 

لتصبح كاملة على النحو التالي:

package com.mzdhr.androidthreadbackgroundtasks.pattern;

import android.os.AsyncTask;

/**
 * Created by MohammadL on 09/1/2019
 * Contact me at [email protected]
 * <p>
 * Take a name, return how many characters in it.
 */
public class MyAsyncTask extends AsyncTask<String, Integer, Long> {

    private static final String TAG = "MyAsyncTask";
    private MyAsyncTaskCallbacks mMyAsyncTaskCallbacks;

    public MyAsyncTask(MyAsyncTaskCallbacks myAsyncTaskCallbacks) {
        mMyAsyncTaskCallbacks = myAsyncTaskCallbacks;
    }


    /**
     * Runs on Main Thread
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * Runs on Background Thread
     */
    @Override
    protected Long doInBackground(String... strings) {
        String name = strings[0];
        Long result = 0L;

        for (int i = 0; i < name.length(); i++) {
            result = result + 1;
            publishProgress(i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return result;
    }

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        if (mMyAsyncTaskCallbacks != null) {
            mMyAsyncTaskCallbacks.onTriggeredProgressUpdate(values[0]);
        }
        super.onProgressUpdate(values);
    }

    /**
     * Runs on Main Thread
     */
    @Override
    protected void onPostExecute(Long aLong) {
        if (mMyAsyncTaskCallbacks != null) {
            mMyAsyncTaskCallbacks.onFinishPostExecute(aLong);
        }
        super.onPostExecute(aLong);
    }

}

 

تشغيل الـ AsyncTask

لتشغيلها نقوم بإستخدام الدالة execute من الـ Activity كالتالي:

new MyAsyncTask(this).execute("Ahmad");

لاحظ طريقة تمرير البيانات لها وهي هنا من نوع String والبيانات هي Ahmad.

 

لإستلام البيانات في الـ Activity:

نعمل implements لواجهتنا المتنصته كالتالي:

public class Main5Activity extends AppCompatActivity implements MyAsyncTaskCallbacks {
٫٫٫

 

نعمل Override للدوالنا كالتالي:

    @Override
    public void onTriggeredProgressUpdate(Integer values) {
        // Update Progress bar here
        Log.d(TAG, "onTriggeredProgressUpdate: " + values);
    }

    @Override
    public void onFinishPostExecute(Long result) {
        // Publish result here
        Log.d(TAG, "onFinishPostExecute: result -> " + result);
    }

من خلالهم نستطيع اخد البيانات وتحديث اي View او عمل مانريد معهم.

 

تشغيل اكثر من AsyncTask في وقت واحد
اي بشكل متوازي (بأستخدام نمط الـ Executor) نقوم بإستخدام الدالة executeOnExecutor بدلاً من الدالة execute. للمزيد من المقارنه انظر اجابات هذا السؤال: AsyncTask execute() or executeOnExecutor().

 

في هذا الدرس راينا طريقة استخدام نمط الـ AsyncTask وطريقة نقل البيانات بأستخدام نمط المراقب. في الدرس القادم سنرى طريقة استخدام نمط الـ Loader.

 

مواضيع ذات صله في عالم البرمجة

 

رابط الكلاسات المستخدمه في هذا الدرس

للحصول على رابط المشروع راجع درس المقدمة.
 

المصادر والمراجع

للمزيد راجع درس المقدمة.
 

نهاية الدرس
لاتنسى تتبع الدرس والدورة كذلك لإشعارك عندما يتم التعديل على المتحوى او اضافة المزيد من المعلومات. ايضاً لاتنسى الاعجاب بالدرس ومشاركته مع الاخرين.

المحاضر

Mohammad Laif

محتوى الدورة

تمهيد
1 مقدمة
2 تعرف على التزامن (الـ Concurrency)
3 اخطاء التزامن (الـ Concurrency) الشائعة
العمليات Processes
1 الهيكلة الهندسية لبيئة نظام الاندرويد
2 طبقة الـ Android Runtime و العمليات Processes
3 انواع العمليات (Processes)
الخيوط الحاسوبية Threads
1 الخيوط الحاسوبية (Threads)
2 مكونات الخيوط الحاسوبية (Thread)
3 انشاء الخيط الحاسوبي
أنماط التصميم للخيوط الحاسوبية
1 انشاء نمط الـ Handler و Looper و Thread
2 انشاء نمط الـ HandlerThreads
3 ماهو نمط الـ Thread Pools (الـ Executors)
4 استخدام نمط الـ Thread Pools كـ Singleton
5 استخدام الـ Callable مع نمط الـ Thread Pools
6 انشاء بركة خيوط حاسوبية Thread Pools لتسريع قاعدة البيانات
7 انشاء نمط الـ AsyncTask الدرس الحالي
8 انشاء نمط الـ Loader
الـ Broadcasts
1 تعرف على الـ Broadcast Receiver
2 انشاء الـ Broadcast Receiver بشكل ثابت
3 انشاء الـ Broadcast Receiver بشكل ديناميكي
4 استخدام الـ Local Broadcast للتخاطب بين المكونات
الـ Services
1 تعرف على الـ Services
2 انشاء الـ Started Service
3 انشاء الـ Intent Service
4 انشاء الـ Bound Service
5 استخدام الـ ResultReceiver للتخاطب مع الـ Intent Service
6 استخدام الـ Broadcast للتخاطب مع الـ Started Service
الـ Alarm Manager
1 تعرف على الـ Alarm Manager
2 طرق استخدام الـ Alarm Manager
الـ Jobs
1 استخدام الـ Android JobSchedualer
2 استخدام الـ Firebase JobDispatcher
3 استخدام الـ WorkManager من حزمة JetPack

الكلمات الدليلية

عن الدرس

0 إعجاب
2 متابع
0 مشاركة
3002 مشاهدات
منذ 5 سنوات

التعليقات (1)

Ebrahim Farouk Taha:

مبدع اخي الكريم 

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !