الشبكات باستخدام HttpURLConnection
الشبكات باستخدام HttpURLConnection
تتبادل كل تطبيقات Android الحديثة تقريبًا البيانات مع خادم بعيد — جلب خلاصة أخبار، ونشر إجراء مستخدم، وتنزيل صورة. قبل أن تصل إلى مكتبة عالية المستوى مثل Retrofit، تحتاج إلى فهم السباكة منخفضة المستوى التي تقوم عليها جميع عمليات HTTP في Android: الفئة java.net.HttpURLConnection. تأتي هذه الفئة مع مكتبة Java القياسية ولا تتطلب أي تبعيات إضافية، مما يجعلها الأداة المناسبة للطلبات الصغيرة أو سكريبتات البناء أو في أي مكان تريد فيه لا شيء زائدًا.
لماذا لا يمكنك إجراء عمليات الشبكة في الخيط الرئيسي
يُشغّل الخيط الرئيسي في Android واجهة المستخدم: يعالج أحداث اللمس، ويقيس طرق العرض ويرسمها، ويُشغّل استدعاءات دورة حياة Activity. أي عملية حجب على هذا الخيط — بما في ذلك استدعاء شبكي قد يستغرق مئات المللي ثانية — يجمّد الواجهة بأكملها. يُطبّق Android هذه القاعدة في وقت التشغيل: محاولة فتح مقبس على الخيط الرئيسي تُطلق استثناء NetworkOnMainThreadException.
في هذا الدرس ستستخدم Thread خلفية بسيطة لإبقاء التركيز على HttpURLConnection نفسها. في الدرس 4 رأيت كيف تمنحك AsyncTask وHandlerThread وExecutorService تجريدات أنظف؛ في التطبيقات الحقيقية ستلف هذا الكود في أحد تلك الأنماط.
إعلان صلاحية الإنترنت
قبل أن يتمكن تطبيقك من فتح أي مقبس، يجب عليك إعلان صلاحية INTERNET في AndroidManifest.xml. على عكس الصلاحيات الخطرة (الكاميرا، جهات الاتصال)، هذه صلاحية عادية: يمنحها Android تلقائيًا ولا تحتاج إلى مطالبة في وقت التشغيل.
نسيان هذا السطر يُنتج فشلاً صامتًا: تنجح عملية استدعاء openConnection() لكن كل استدعاء لـconnect() يُطلق java.net.SocketException: Permission denied. أضفها دائمًا قبل كتابة أي كود شبكي.
إجراء طلب GET خطوة بخطوة
يتبع طلب GET الأساسي مع HttpURLConnection ست خطوات: بناء URL، وفتح الاتصال، وتهيئته، والاتصال، وقراءة الاستجابة، وإغلاق الدفق. إليك مثالًا كاملاً مستقلًا يجلب حمولة JSON من API عام:
بعض التفاصيل تستحق الانتباه:
setConnectTimeoutمقابلsetReadTimeout: يتحكم مهلة الاتصال في المدة التي ينتظرها نظام التشغيل لإتمام المصافحة الثلاثية TCP. يتحكم مهلة القراءة في المدة التي ينتظر فيها البيانات بعد إنشاء الاتصال. كلاهما يُعيّن إلى الصفر (لانهائي) افتراضيًا إذا لم تُحددهما، مما يعني أن خيط الخلفية قد يحجب إلى الأبد على خادم معطوب.getResponseCode()يُطلق الطلب: استدعاء هذه الطريقة هو النقطة التي يُرسل فيهاHttpURLConnectionالطلب فعليًا وينتظر رؤوس الاستجابة. الاستدعاءات قبل هذه النقطة تُهيئ كائن الاتصال فحسب.- دفق الخطأ: عندما يُعيد الخادم 4xx أو 5xx، يكون الجسم على
getErrorStream()لا علىgetInputStream(). قراءته تتيح لك إظهار رسائل خطأ API مفيدة للمطور. disconnect()في finally: هذا يُغلق المقبس الأساسي. تخطيه يُسرّب موارد نظام التشغيل، خاصة على الشاشات المزدحمة التي تُجري طلبات كثيرة.
إرسال طلب POST بجسم JSON
لطلب POST يجب تهيئة ثلاثة أشياء إضافية: تعيين الطريقة إلى "POST"، وتفعيل الإخراج بـsetDoOutput(true)، وكتابة بيانات الجسم إلى getOutputStream(). لن يُرسل الاتصال حتى تقرأ رمز الاستجابة.
Content-Type قبل استدعاء getOutputStream(). بمجرد أن تبدأ في كتابة الجسم، يُثبّت HttpURLConnection رؤوس الطلب. تعيين الرؤوس بعد هذه النقطة لن يكون له أي تأثير بصمت.
قراءة رمز الاستجابة ومعالجة الأخطاء
تُخبرك رموز حالة HTTP بالنتيجة الدلالية لطلب. يجب أن يتعامل كودك على الأقل مع ثلاث فئات:
- نجاح 2xx — اقرأ
getInputStream()للحصول على الجسم. - خطأ العميل 4xx (404 غير موجود، 401 غير مُصرح، 422 غير قابل للمعالجة) — طلبك كان خاطئًا؛ اقرأ
getErrorStream()للحصول على رسالة API وأظهرها للمطور أو المستخدم. - خطأ الخادم 5xx — الخادم معطوب؛ أعد المحاولة مع تراجع أسّي، أو تدهور بأمان.
201 Created هو الاستجابة القياسية لطلب POST ناجح يُنشئ موردًا. تحقق دائمًا من النطاق الكامل 2xx (status >= 200 && status < 300) بدلاً من الترميز الصارم == 200.
تسليم النتائج إلى واجهة المستخدم
يعمل استدعاء الشبكة على خيط خلفي، لكن جميع تحديثات العرض — تعيين النص، إظهار شريط التقدم، التنقل — يجب أن تحدث على الخيط الرئيسي. الطريقة القانونية للقفز إلى الخلف هي new Handler(Looper.getMainLooper()).post(runnable). إذا كنت داخل Activity، يمكنك استخدام الاختصار المعادل runOnUiThread(runnable).
HTTPS وحركة المرور النصية الواضحة
اعتبارًا من Android 9 (API 28)، يُحظر حركة المرور النصية الواضحة (HTTP العادي) افتراضيًا. يجب أن يستخدم تطبيقك HTTPS لجميع عناوين URL الإنتاجية. أثناء التطوير يمكنك السماح مؤقتًا بالنص الواضح لمضيف تصحيح محدد بإضافة network_security_config.xml، لكن لا تشحن هذا التكوين إلى الإنتاج أبدًا.
الخلاصة
كل استدعاء HTTP في Android يعيش على خيط خلفي — NetworkOnMainThreadException أمر غير قابل للتفاوض. تمنحك HttpURLConnection تحكمًا دقيقًا في طريقة الطلب والرؤوس والمهل والجسم. الحلقة الأساسية هي: فتح، وتهيئة، واستدعاء getResponseCode()، وقراءة الدفق المناسب، وقطع الاتصال في كتلة finally، وإرسال النتيجة إلى الخيط الرئيسي عبر Handler. بمجرد أن تشعر بالارتياح مع هذه السباكة، يفحص الدرس التالي Retrofit، الذي يُؤتمت هذا النمط المعياري مع الحفاظ على جميع المفاهيم الأساسية ذاتها.