طريقة ترتيب المهام في برمجة تطبيقات الاندرويد

مقالة تشرح طريقة ترتيب المهام في برمجة تطبيقات الاندرويد على افضل وجه

Mohammad Laifمنذ شهر

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

 

مقاله تشرح طريقة ترتيب المهام في برمجة تطبيقات الاندرويد على افضل وجه. وتضمن لنا تشغيل مهامنا في خيوط حاسوبية خلفيه, وهذا يعني تطبيق اسرع واكثر سلاسه واقل مشاكل.

 

ماذا نقصد بالمهام؟

نقصد بالمهام كل مهمة يقوم تطبيقنا بعملها بشكل متكرر كإظهار الاشعار للمستخدم او اخد نسخة احتياطية لبيانات المستخدم يومياً في منتصف الليل. وهذه قائمة لبعض امثلة المهام:

  • إظهار اشعارات للمستخدم.
  • مزامنة قاعدة البيانات.
  • تحميل شئ من الشبكة العنكبوتية.
  • حفظ نسخة احتياطية من قاعدة البيانات.
  • معالجة صورة نقطية.
  • تشفير.

اذن نستطيع فهم المهام على انها عبارة عن اسطر برمجية نقوم بكتابتها لعمل شئ متكرر في المشروع.
 

ماذا سننشئ؟

 

خطوات يجب الانتباه لها لفهم هذه الطريقة

اولاً: تقوم هذه الطريقة على عمل تغليف Encapsulate والكثير من التجريد Abstraction (هنالك جزئية عن مفهوم التغيف في المقالة: تبسيط مفهوم الـ Abstraction في البرمجة) للإكواد المتشابهه في كلاس واحد. اي اننا ننشئ كلاس مختصه بشئ ما, نضع بها جميع الدوال بشكل Static ونناديها عند الحاجة لها. كما فعلنا وإنشئنا كلاس خاصه في الاشعارات وإظهارهم في المقالة السابقه.

ثانياً: هذه الطريقة تسمح لنا بتشغيل المهام في خيط حاسوبي مختلف عن الخيط الرئيسي, فسوف نقوم بإستخدام الـ Intent Service.

 

الية عمل هذه الطريقة

  • سنقوم بعمل Intent تحتوي على اسم المهمة, وذلك بإستخدام الدالة setAction.
  • تلك الـ Intent ستقوم بتشغيل كلاس TaskService اي الـ Intent Service مما سيصنع لنا خيط حاسوبي خلفي.
  • وبه سنقوم بتمرير الـ Action الذي وضعناه في الـ Intent الى كلاس ترتيب المهام AppTask وذلك من خلال دالتها executeTask.
  • ومن ثم ستقوم بدورها الدالة executeTask بتشغيل الشفرة البرمجية التي تطابق ذلك الـ Action في خيط حاسوبي خلفي.
  • وربما طابق ذلك الـ Action شفرة إظهار الاشعارات, وهكذا سوف نقوم بنداء الدوال المسؤوله عن ذلك في كلاس الاشعارات NotificationUtils.

 

إنشاء كلاس الـ NotificationUtils

قمنا بإنشائها في مقالة سابقة بعنوان: برمجة الاشعارات في تطبيقات الاندرويد.

 

إنشاء كلاس الـ TaskService

قمنا بشرح طريقة إنشاء هذا النوع من الخدمات في الدرس: انشاء الـ Intent Service .

الشفرة المصدرية للكلاس:

public class TaskService extends IntentService {

    public TaskService() {
        super("TaskService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getAction();
        AppTask.executeTask(this, action, intent.getExtras());

    }
}
  • لاحظ اننا سوف نستقبل الـ Intent التي ستقوم بتشغيل هذه الكلاس الخدميه في الدالة onHandleIntent.
  • سنقوم بإستخراج اسم المهمة وإستخدام الدالة executeTask لتشغيل المهمة المطلوبه.
  • تذكر تشغيل هذه الكلاس سينشئ لنا خيط حاسوبي خلفي بإسمها.

 

إنشاء كلاس المهام الـ AppTask

الحقول:

public static final String ACTION_SHARE = "clear_all_notifications";
public static final String ACTION_CLEAR_ALL_NOTIFICATION = "clear_all_notifications2";
public static final String ACTION_BACKUP_DATABASE = "backup_database";
public static final String ACTION_SYNC_ROOM_FIREBASE_DATABASE = "sync_room_firebase_database";
public static final String ACTION_FETCH_API = "fetch_api";
  • حقول عامة لإستخدامها في كلاسات متعددة تعبر عن اسم المهام المراد القيام بها في تطبيقنا.

 

إنشاء Constructor:

private AppTask() {}
  • بشكل خاص حتى لانخطئ ونعمل Init لعناصر هذه الكلاس في مشروعنا. فنحن لانحتاج لإنشاء عناصر منها. سنقوم بإستخدام دوالها كـ Static في إي جزئية نريد.

 

كتابة دالة الـ executeTask:

public static void executeTask(Context context, String action, Bundle extras) {
    if (ACTION_CLEAR_ALL_NOTIFICATION.equals(action)) {
        clearNotifications(context);
    } else if (ACTION_SHARE.equals(action)) {
        shareNotification(context, extras);
    } else if (ACTION_BACKUP_DATABASE.equals(action)) {

    } else if (ACTION_SYNC_ROOM_FIREBASE_DATABASE.equals(action)) {

    } else if (ACTION_FETCH_API.equals(action)) {
        fetchAPI();
    }

}
  • يتم النداء على هذه الدالة من خلال كلاس الـ IntentService في خيط حاسوبي خلفي. وتمرير اسم المهمة المراد تشغيلها من خلال اسم الـ Action.
  • اذا طابق اسم الـ Action احد الجمل الشرطية If فسوف تبدء بتشغيل الشفرة البرمجية للمهمة في الخلفية.

 

دوال الشفرة البرمجية للمهام:

private static void clearNotifications(Context context) {
    NotificationUtils.clearNotifications(context);
}

private static void shareNotification(Context context, Bundle extras) {
    NotificationUtils.shareNotification(context, extras.getString("TITLE_KEY"), extras.getString("BODY_KEY"));
}

private static void fetchAPI() {
    APIUtils.fetchDataFromAPI();
}
  • نستطيع كتابة المنطق للمهام هنا. ولكنا قمنا بتغليفة في كلاسات مخصصة لكل مهمة.

 

الكلاس كاملة:

public class AppTask {
    public static final String ACTION_SHARE = "clear_all_notifications";
    public static final String ACTION_CLEAR_ALL_NOTIFICATION = "clear_all_notifications2";
    public static final String ACTION_BACKUP_DATABASE = "backup_database";
    public static final String ACTION_SYNC_ROOM_FIREBASE_DATABASE = "sync_room_firebase_database";
    public static final String ACTION_FETCH_API = "fetch_api";

    private AppTask() {
    }

    public static void executeTask(Context context, String action, Bundle extras) {
        if (ACTION_CLEAR_ALL_NOTIFICATION.equals(action)) {
            clearNotifications(context);
        } else if (ACTION_SHARE.equals(action)) {
            shareNotification(context, extras);
        } else if (ACTION_BACKUP_DATABASE.equals(action)) {

        } else if (ACTION_SYNC_ROOM_FIREBASE_DATABASE.equals(action)) {

        } else if (ACTION_FETCH_API.equals(action)) {
            fetchAPI();
        }

    }


    /**
     * Tasks
     */
    private static void clearNotifications(Context context) {
        NotificationUtils.clearNotifications(context);
    }

    private static void shareNotification(Context context, Bundle extras) {
        NotificationUtils.shareNotification(context, extras.getString("TITLE_KEY"), extras.getString("BODY_KEY"));
    }

    private static void fetchAPI() {
        // APIUtils.fetchDataFromAPI(context);
    }

}

 

طريقة الاستخدام

السيناريو الاول:

نريد استخدامها لجلب البيانات من الانترنت عندما يقوم المستخدم بفتح التطبيق, او عندما يضغط على زر ما.

التسلسل البرمجي:

  1. الخيط الرئيسي: في زر ما! نقوم بصناعة Intent تقوم بتشغيل الكلاس TaskService ونرفق معها اسم المهمه كـ Action.
  2. الخيط الخلفي: ستقوم الـ TaskService بصناعة خيط حاسوبي خلفي بإسمها.
  3. الخيط الخلفي وتشغيل المهمة المطلوبة: بعد صناعة خيط حاسوبي خلفي, ستقوم الدالة executeTask بتشغيل الشفره البرمجيه للمهمه المراده في الخيط الخلفي (حسب اسم الـ Action في الخيط الحاسوبي الخلفي).

 

اذا كنت تواجه صعوبه في فهم ماهي هذه الخيوط والكلاسات الخدمية وتوقيت المهام انصحك بالاطلاع على الدروس: التزامن في نظام الاندرويد.

 

الشفرة المصدريه:

Intent intent = new Intent(MainActivity.this, TaskService.class);
intent.setAction(AppTask.ACTION_FETCH_API);
startService(intent);

 

السيناريو الثاني:

لدينا كلاس اشعارات نريد وضع زر به, عندما يضغط المستخدم على الزر تتفعل المهمه المطلوبة.

التسلسل البرمجي:

  1. الخيط الرئيسي: في زر ما! توقيت إظهار الاشعار بعد دقيقتان من خلال الـ Work Manager.
  2. بركة خيوط: بعد دقيقتان عندما يحين الوقت سيقوم الـ Work Manager بتشغيل شفرة إظهار الاشعار في بركة من الخيوط (لتتعرف على بركة الخيوط اطلع على ماهو نمط الـ Thread Pools (الـ Executors)).
  3. تلك البركة ستحوي الـ Intent Pending في الخلفيه وستنشط عندما يضغط المستخدم على احد الازرار في الاشعار.
  4. المستخدم يضغط على احد ازرار الاشعار: عندما يضغط المستخدم على احد ازرار الاشعار هذا سيفعل الـ Intent الموجوده بداخل الـ Pending Intent. وهذا سيؤدي الى تشغيل كلاس الـ TaskService.
  5. خيط خلفي: ستقوم الـ TaskService بصناعة خيط حاسوبي خلفي بإسمها, ثم ستقوم بتشغيل الشفرة البرمجية المرادة.
  6. تشغيل المهمة: بعد صناعة خيط حاسوبي خلفي, ستقوم الدالة executeTask بتشغيل الشفرة البرمجية للمهمه المراده (حسب اسم الـ Action في الخيط الحاسوبي الخلفي).

 

الشفرة المصدريه:

في مكان ما بالـ MainActivity نقوم بتوقيت إظهار الاشعار بعد دقيقتان بإستخدام الـ Work Manager استخدام الـ WorkManager من حزمة JetPack:

        Constraints constraints = new Constraints.Builder()
                .build();

        OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(TimerWithWorkManager.class)
                .setConstraints(constraints)
                .addTag("MY_WORK_MANAGER_TAG_ONE_TIME")
                .setInitialDelay(2, TimeUnit.MINUTES)
                .build();

        WorkManager.getInstance().enqueue(oneTimeWorkRequest);

 

كلاس الـ Work التي ستقوم بالعمل بعد جدولته:

public class TimerWithWorkManager extends Worker {
    public TimerWithWorkManager(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        NotificationUtils.showNotification(getApplicationContext(), "MyTitle", "MyBody");
        return Result.success();
    }
}

 

داله في كلاس الـ NotificationUtils لصنع الزر في الاشعار, التي ستحتوي على الـ Pending Intent والـ Intent التي ستقوم بتشغيل كلاس TaskService:

    private static NotificationCompat.Action actionShareNotification(Context context, String title, String body) {
        Intent intent = new Intent(context, TaskService.class);
        intent.setAction(AppTask.ACTION_SHARE);
        intent.putExtra("TITLE_KEY", title);
        intent.putExtra("BODY_KEY", body);

        PendingIntent pendingIntent = PendingIntent.getService(
                context,
                PENDING_INTENT_ID,
                intent,
                PendingIntent.FLAG_CANCEL_CURRENT
        );

        NotificationCompat.Action action = new NotificationCompat.Action(
                R.drawable.notification_share_icon,
                context.getString(R.string.share),
                pendingIntent
        );

        return action;
    }

 

كلاس الخدمة التي سوف تتفعل عندما يضغط المستخدم على زر الاشعار:

public class TaskService extends IntentService {

    private static final String TAG = "TaskService";

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     **/
    public TaskService() {
        super("TaskService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getAction();
        AppTask.executeTask(this, action, intent.getExtras());

    }
}

 

مره اخرى كلاس المهام AppTask التي تحتوي على الشفرة البرمجية للمهمة المراده:

public class AppTask {
    public static final String ACTION_SHARE = "clear_all_notifications";
    public static final String ACTION_CLEAR_ALL_NOTIFICATION = "clear_all_notifications2";
    public static final String ACTION_BACKUP_DATABASE = "backup_database";
    public static final String ACTION_SYNC_ROOM_FIREBASE_DATABASE = "sync_room_firebase_database";
    public static final String ACTION_FETCH_API = "fetch_api";

    private AppTask() {
    }

    public static void executeTask(Context context, String action, Bundle extras) {
        if (ACTION_CLEAR_ALL_NOTIFICATION.equals(action)) {
            clearNotifications(context);
        } else if (ACTION_SHARE.equals(action)) {
            shareNotification(context, extras);
        } else if (ACTION_BACKUP_DATABASE.equals(action)) {

        } else if (ACTION_SYNC_ROOM_FIREBASE_DATABASE.equals(action)) {

        } else if (ACTION_FETCH_API.equals(action)) {
            fetchAPI();
        }

    }


    /**
     * Tasks
     */
    private static void clearNotifications(Context context) {
        NotificationUtils.clearNotifications(context);
    }

    private static void shareNotification(Context context, Bundle extras) {
        NotificationUtils.shareNotification(context, extras.getString("TITLE_KEY"), extras.getString("BODY_KEY"));
    }

    private static void fetchAPI() {
        // APIUtils.fetchDataFromAPI(context);
    }

}

 

واخيراً تنفيذ الشفرة البرمجية للمهمة الموجودة التي قمنا بتغليفها في كلاس الـ NotificationUtils:

    public static void shareNotification(Context context, String title, String body){
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(title);
        stringBuilder.append("\n");
        stringBuilder.append(body);

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, stringBuilder.toString());
        context.startActivity(intent.createChooser(intent, "Choose the app you want to share on it"));
    }

 

وهكذا قمنا بالوصول الى نهاية المقالة.

 

المصدر:

  • لايوجد لها اسم حالياً ولكنها تتشابه كثيراً مع نمط الـ action/reduction في الرياكت. 
  • رأيت استخدام هذه الطريقة في عدة مشاريع للإندرويد لشركات كـ Google وغيرها.
  • الطريقة مكتسبه من خلال دورة النانو ديجري على منصة يوداستي للتعليم. تستطيع قرائة تجربتي في المقالة: تجربتي في دراسة الـ Android Developer Nanodegree.
كلمات دليلية: android java
2
إعجاب
133
مشاهدات
0
مشاركة
2
متابع

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

ابو يوسف الحازمي:

الله يسعدك ويبارك في علمك وعملك

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

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