هل يوجد درس بسيط لكيفية ارسال واستقبال بيانات بين الاندرويد و السيكول سيرفر باستخدام الويب سيرفيس
- محمد ششتاوى
- 2015-05-12T01:30:08+00:00
السلام عليكم - ولله الحمد افهم الويب سيرفيس واستطيع التعامل مع السيكول سيرفر جيدا وايضا اكتب جافا وافهم بيئة الاندرويد
لكنى بحثت كثيرا عن شرح فى الاندرويد يشرح كيفية ارسال واستقبال جيسون او XML بين الاندرويد والويب سيرفيس
فهل من مساعدة رجاء فى هذه الجزئية ؟
من خبرتي المتواضعة ببرمجة الأندرويد فقد جربت سابقا استخدام طلبات ال POST request إلى صفحة PHP بسيرفر الويب لإرسال البيانات واستقبالها، ربما قد تجدي نفعا معك أو يستفيد منها أشخاص آخرون.
هذا كلاس خاص قمت بتصميمه لمشروع تجريبي من أجل عمليات الاتصال بسيرفر الويب بالاسم ServerLink، وهو يعتمد في عمله على الكلاس AsyncTask بمكتبات التطوير، يضاف الكلاس مباشرة إلى مشروع الأندرويد مع تعديل معرف الحزمة ال package إلى المعرف الخاص بتطبيقك، كمثال إذا أردت ارسال سلسلة تحتوي اسمك إلى السيرفر فإنك تكتب الصيغة التالية:
new ServerLink(this, "hello", "Muhammad");
المعامل الأول مؤشر لل Activity التي قامت بإنشاء كائن الاتصال، حتى يستطيع الأخير الوصول لها وعرض التحديثات التي تظهر لمستخدم التطبيق أوغيره عبر متغير activity الخاص كما سيتبين تباعا.
المعامل الثاني يعمل كمفتاح أو دليل لنوع اجراء الاتصال، القيمة hello ترشد الكائن لإجراء الاتصال بواسطة الكلاس الداخلي RunHello
المعامل الثالث يمثل الرسالة أو المحتوى الذي يتوجب على الإجراء إرساله للسيرفر، هنا توجد قيمة واحدة وهي اسمك، لارسال قيمة أخرى ببساطة أضفها كمعاملات إضافية، أي أنك لست مقيدا، فقط تأكد من برمجة الكلاس الداخلي المخصص للإجراء لاستيعاب هذه القيم.
هذا هو كود كلاس الاتصال ServerLink
package com.test.app;
import android.app.Activity;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ServerLink
{
//server name or IP address of your web server
private static String server = "http://192.168.10.2";
private Activity activity;
ServerLink(Activity activity, String toDo, String... params) {
this.activity = activity;
switch (toDo) {
case "hello":
new RunHello().execute(params);
break;
//you can put other "case" here ended by "break"
}
}
private class RunHello extends AsyncTask <String, Void, HttpEntity> {
private ProgressBar pb = (ProgressBar) activity.findViewById(R.id.progressBar);
protected void onPreExecute() {
pb.setVisibility(View.VISIBLE);
}
protected HttpEntity doInBackground(String... params) {
HttpClient client = new DefaultHttpClient();
// "hello.php" is the page that will handel POST request
HttpPost post = new HttpPost(ServerLink.server + "hello.php");
try {
List<NameValuePair> pairs = new ArrayList<NameValuePair>();
pairs.add(new BasicNameValuePair("thename", params[0]));
post.setEntity(new UrlEncodedFormEntity(pairs, HTTP.UTF_8));
HttpResponse response = client.execute(post);
return response.getEntity();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
protected void onPostExecute(HttpEntity ent) {
pb.setVisibility(View.GONE);
try {
if(ent != null) {
Toast.makeText(activity, EntityUtils.toString(ent), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(activity, "can not reach the server", Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
نأتي لبعض من التفصيل
المتغير server يحوي عنوان سيرفر الويب، المتغير toDo يمثل القيمة أو المفتاح الذي يحدد نوع الإجراء المطلوب وأي من الكلاسات الداخلية ينفذ وذلك من خلال جملة switch ، هنا مبرمجة على أنها إن استقبلت المفتاح "hello" فإن الكلاس الداخلي RunHello سوف ينفذ.
لاحظ أنه عند انشاء كائن من الكلاس الداخلي الخاص بالإجراء فإنه يمرر المعامل params، وهي عبارة عن مصفوفة بها القيم التي تود ارسالها بطلب ال POST لسيرفر الويب، فمثلا params[0] تحتوي القيمة "Muhammad" ، وإن أرسلت قيمة ثانية وثالثة عند انشاء كائن الاتصال فستجدهما في params[1] و params[2] وهكذا.
HttpPost post = new HttpPost(ServerLink.server + "hello.php");
هنا تم برمجة الإجراء الداخلي RunHello على أن يرسل القيم بالمصفوفة params إلى صفحة hello.php الموجودة بسيرفر الويب لمعالجتها.
pairs.add(new BasicNameValuePair("thename", params[0]));
أما هنا فقد تم ربط القيمة params[0] بالمسى thename، وهو الاسم الذي سيستقبله سيرفر الويب ك $_POST['thename'] وستكون قيمته "Muhammad"
if(ent != null) {
Toast.makeText(activity, EntityUtils.toString(ent), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(activity, "can not reach the server", Toast.LENGTH_SHORT).show();
}
هنا اختبار لوجود رد من السيرفر، سيعرض رد السيرفر إن وجد على شكل رسالة Toast بواسطة EntityUtils.toString(ent) وإلا فسيعرض "can not reach the server" ، رد السيرفر يكون بكود PHP على شكل echo أو print مثلا. بالطبع يمكنك بدل أن تعرض رسالة القيام بتفاعل ما على واجهة المستخدم مثل عرض النتيجة في EditText أو TextView بالواجهة، والوصول للواجهة يكون عن طريق المتغير activity ، في هذا المثال افترض وجود ProgressBar بالواجهة تحت المعرف R.id.progressBar وتم ربطه بالمؤشر pb ، ثم إظهاره عند بدء الإجراء وإخفائه عند الإنتهاء كالتالي:
private ProgressBar pb = (ProgressBar) activity.findViewById(R.id.progressBar);
pb.setVisibility(View.VISIBLE);
pb.setVisibility(View.GONE);
أخيرا:
يمكنك إنشاء أكثر من كلاس داخلي كإجراءات مختلفة مع سيرفر الويب، مثلا كإرسال رقم معرف لسجل ما بقاعدة بيانات MySQL واستقبال بيانات السجل وعرضها بالتطبيق، حيث سيكون هنالك ملف PHP وسيط بين التطبيق وبين قاعدة البيانات، الأمر أشبه بأسلوب العمل مع الواجهات البرمجية API
هذه ملاحظاتي:
1- استخدام Apache HTTP client غير محبذ. ال classes شبه deprecated من جووجل ولا مزيد من عمليات التطوير.
2- AsyncTask تستخدم Shared Thread عند استدعاء execute دون تمرير Executor. بمعنى لو قمت باستخدامه في Timeline مثلا، فان صور المستخدمين ستتأتي واحدة واحدة. أيضا لن تتمكن من الإنتقال الى الصفحة التالية دون الإنتهاء من تحميل جميع الصور.
3- الإستخدام لل HTTP stack بهذا الشكل ، يعني أنك لن تحصل على:
Caching support : لماذا تقوم بتحميل /user/sudanix أكثر من مرة؟
Prioritization support : حتى تعطي أولية لتحميل الصفحة قبل الصور مثلا.
Cancellation support : بالإستخدام السابق يصعب ذلك.
Image loading support
Parallel Request support
Retry Request support
4- التصميم السابق يحوي issue معروفة ، مثلا أثناء ال POST request قام المستخدم بتغيير ال configuration (قلب الشاشة مثلا) بالتالي سيتم إنشاء Activity جديدة. أما ال Activity السابقة عند محاولة استخدامها من قبل onPostExecute سيحصل لك Crash في التطبيق.
كذلك توجد العديد من issues التي لا مجال لذكرها هنا.
لذلك أقول ان استخدام HTTP library build on top of HTTP client هو الخيار الأمثل.
مثلا تطبيق Twitter يستخدم Retrofit لعمليات ال HTTP.
تطبيق Google Play يستخدم Volley
والعديد من الأمثلة.
هذا مثال ل Volley اخذته من أحد المشاريع وأفضت اليه بعض ال comments حتى يسهل قرائته.
ملاحظات مهمة فعلا وسأضعها قيد الإعتبار، ربما لأن احتياجاتي بسيطة تقتصر على ارسال واستقبال بيانات بسيطة فلم أواجه مشكلة، ليس الأمر معقدا كالتعامل مع الصور أو العرض المتسلسل بال Timeline.
بالنسبة لمشكلة قلب الشاشة أثناء عمل ال AsyncTask فقد وقفت عليها بنفسي، وقررت عندها استخدام الكود التالي داخل دالة onCreate بالأكتيفيتي :
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
وذلك أجبر الواجهة على الثبات بالوضع العمودي مهما كانت وضعية الهاتف، طبعا هذا ليس بحل عملي اطلاقا وإنما مسكن خفيف فحسب...
توجد حلول لهذه المشكلة، أحدها يعتمد على إعادة تعيين ال Activity الجديدة لل AsyncTask وذلك بحفظ ال reference لها في onSaveInstance او save retain object. وعندما يتم استدعاء onCreate تتم عملية الربط بعد الحصول على ال reference السابق.
أيضا يوجد حل أبسط، وهو وضع activity =null في الدالة onStop وعند استدعاء onPostExecute يتم فحص if (activity != null).
مشكووووووووووووور جدا اخى مصطفى التاج لقد اسعدتنى كثيرا بهذا المجهود الرائع والشرح الوافى وربنا يجازيك كل خير
سوف اطبق اخى الكريم هذا الشرح واذا استشكل معى اى شىء سارجع لاناقشك فيه - وشكرا لك جزيلا
مجتمع مطوري ومبرمجي نظام أندرويد
التعليقات