0%

[برمجة اندرويد] تنفيذ العمليات في الخلفية

عند تنفيذ تطبيق الاندرويد يتم إنشاء عملية أساسية تسمى UI Thread وهي المسؤولة في تنفيذ أوامر التطبيق وعرض النتائج على الشاشة وكذلك الاستجابة لعمليات المستخدم مثل الضغط على الأزرار والكتابة …الخ. في التطبيقات البسيطة تكون تلك العملية قادرة على أداء المهمة بشكل جيد لكن توجد حالات كثيرة يحتاج فيها مطور التطبيق إلى إنشاء عمليات إضافية لكون العملية الأساسية UI Thread غير كافية.

لنفرض مثلا أن التطبيق يقوم بتحميل ملف ويعرض نسبة التحميل على الشاشة بشكل مباشر فهذا يعني أننا نحتاج إلى مراقبة حالة الملف وتعديل البيانات المعروضة على الشاشة بشكل مستمر، لو نفذنا هذه الخطوات باستخدام العملية الأساسية فستجد أن التطبيق سيصبح مشغول في حلقة برمجية (loop) لمراقبة عملية تحميل الملف وتعديل بيانات العرض ولن يكون هناك وقت للتعامل مع المستخدم أو بقية أجزاء التطبيق بمعنى أن التطبيق سيكون في حالة عدم استجابة إلى أن يكتمل الملف وتنتهي الحلقة البرمجية. وستلاحظ كذلك أن حالة تحميل الملف الظاهرة على الشاشة لن تتحدث وستكون صفر عند البداية وستبقى كذلك إلى أن يكتم التحميل وستقفز فجأة إلى 100% والسبب أن العملية الأساسية كما ذكرنا مشغولة بعملية التحميل ولا تستطيع تحديث شاشة التطبيق ليرى المستخدم نسبة تحميل الملف. نفس المشكلة لو كان البرنامج عبارة عن ساعة زمنية أو يقوم بتحريك رسومات أو لعبة.

الحل سهل، وهو بإنشاء عملية أخرى تعمل في الخلفية تقوم هي بمتابعة عملية تحميل الملف وترسل تحديثات عن حالة التحميل إلى العملية الأساسية وهذه العملية تسمى AsyncTask وتعني العملية غير المتزامنة لأنها تعمل بشكل مستقل عن العملية الأساسية.

عند إنشاء تلك العملية تحتاج أن تحدد لها أنواع القيم المستخدمة حسب الترتيب التالي:

  1. نوع القيم التي سترسل للعملية
  2. نوع القيم التي سترسلها العملية إلى العملية الأساسية
  3. نوع قيم النتيجة النهائية

تلك الأنواع ليست ضرورية بل حسب حاجة التطبيق وفي حال عدم الحاجة نكتب مكانها Void. للعملية التزامنية 4 دوال كالتالي:

  • onPreExecute() تعمل في العملية الأساسية قبل ابتداء العملية الخلفية وهي اختيارية
  • doInBackground(Params) هذه هي الدالة الأهم إذ هنا ننفذ النص الذي نريد أن يعمل في الخلفية ومنه يمكن تنفيذ الأمر publishProgress(Progress) لإرسال التحديثات للعملية الأساسية.
  • onProgressUpdate(Progress) هذه الدالة تعمل في العملية الأساسية وتستقبل ما يتم ارسالة من العملية التي تعمل في الخلفية وتستخدم لتحديث البيانات وعرضها للمستخدم وهي اختيارية.
  • onPostExecute(Result) دالة اختيارية تعمل في العملية الأساسية بعد انتهاء العملية الخلفية وتستخدم الاستقبال الناتج النهائي.

سأعرض مثال بسيط واشرحه، المثال عبارة عن دالة اسمها startAsync يمكن تنفيذها بضغطة زر مثلا. الدالة تتعامل مع ثلاث عناصر في واجهة التطبيق، عنصري (textView3 و textView4) و شريط تقدم (progressBar2).

قبل تنفيذ العملية الخلفية يقوم البرنامج بتغيير عنصر نص3 إلى كلمة “بدأ” ثم يتم إنشاء عملية غير تزامنية جديدة حيث تقوم في الخلفية بالعد من 1 إلى 50 كل 100 جزء من الثانية و وإرسال الرقم الحالي إلى العملية الأساسية عن طريق publishProgress والتي بدورها تستدعي onProgressUpdate لكتابة ذلك الرقم في عنصر نص4 وكذلك تحديث نسبة الشريط المتقدم بنفس قيمة الرقم. ثم بعد النص الخاص بالعملية الخلفية نقوم مرة أخرى بتغيير عنصر نص3 والذي كتبنا به كلمة “بدأ” إلى كلمة “خطوة 2”. هذا هو النص البرمجي واسفل منه سأذكر الناتج من تنفيذه. (رابط مباشر للنص)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void startAsync(View view) {
final TextView textView3 = (TextView) findViewById(R.id.textView3);
final TextView textView4 = (TextView) findViewById(R.id.textView4);
final ProgressBar progressBar2 = (ProgressBar) findViewById(R.id.progressBar2);

textView3.setText("بدأ");
new AsyncTask<Void, Integer, Void>() {
@Override
protected Void doInBackground(Void...nothing) {
for (int i=1 ; i<51 ; i++) {
publishProgress(i);
SystemClock.sleep(100);
}
return null;
}
protected void onProgressUpdate(Integer... value) {
textView4.setText(value\[0\].toString());
progressBar2.setProgress(value\[0\]);
};
}.execute();

textView3.setText("خطوه 2");
}

عند تنفيذ هذه الدالة ستجد أن عنصر النص4 بدأ بالعد من 1 إلى 50 بشكل منتظم وكذلك شريط التقدم يتزايد إلى أن يصل لقيمته القصوى 50 وكل ذلك دون التأثير على أداء التطبيق العام حيث يمكنك في نفس الوقت التفاعل مع التطبيق وسيستجيب بشكل طبيعي كالضغط على أزرار أو الكتابة …الخ. لكن لو تابعت عنصر نص3 في واجهة التطبيق ففي الغالب أنك لن ترى كلمة “بدأ” بل سترى كلمة “خطوة 2” بمجرد أن تكبس على الزر والسبب هو أن التطبيق كتب كلمة “بدأ” ثم قام بتشغيل العملية الخلفية لكنه لم ينتظر إلى أن تنتهي فلا حاجة لذلك بل قام مباشرة بإكمال تنفيذ النص وكتب كلمة “خطوة 2” لذلك لن تستطيع رؤية كلمة “بدأ” لأنها استبدلت بسرعة.

التطبيق الكامل يحتوي على جزئين، الجزء الاول يقوم بالعد الى 50 لكن باستخدام العملية الأساسية (الواجهة) فتجد التطبيق يتجمد ولا يمكن ضغط اي زر فيه ولا يتم تحديث العداد ولا شريط التقدم. بينما الجزء الثاني من التطبيق يستخدم AsyncTask للعد في الخلفية وارسال حالة العداد الى الواجهة ليتم تحديثها فتجد البرنامج يعمل بشكل تفاعلي كما في الصورة المتحركة التالية:

AsyncTask

النص الكامل للبرنامج موجود هنا https://github.com/fduraibi/AsyncTask_Example

ويمكنك الحصول على البرنامج (ملف apk) للتجربة من هنا https://github.com/fduraibi/AsyncTask_Example/releases/latest