رتبه موضوع:
  • 0 رای - 0 میانگین
  • 1
  • 2
  • 3
  • 4
  • 5
آموزش اتصال به وب‌سرویس در اندروید
#1
با سلام، بنا به درخواست زیاد دوستان، تصمیم گرفتم درقالب یک تاپیک آموزشی، نحوه‌ی کار با وب‌سرویس رو در اندروید توضیح بدم. البته برای این‌کار از کلاس‌های داخلی خود اندروید و بدون هرگونه کتابخانه‌ی جانبی استفاده میکنیم تا این آموزش در پایه‌ای‌ترین سطح خودش باقی بمونه و برای همه قابل استفاده باشه.

برای مدیریت بهتر مطالب، هر بخش از کار رو درقالب یک پست جداگانه توضیح میدم.
تشکر شده توسط: miladsss , Mostafa4hc , ilbeygi.m
#2
ایجاد وب‌سرویس در سمت سرور

برای این‌کار من از PHP استفاده میکنم که رواج بیشتری داره ولی هیچ تفاوتی نمیکنه که وب‌سرویستون رو با چی بسازین. یه وب‌سرویس ساده میسازیم که وقتی بهش وصل میشیم، میتونه دو تا کار انجام بده:
  1. ثبت یک نام و شماره تلفن
  2. جستجوی یک نام و پیداکردن شماره تلفن از دیتابیس
برای اینکار فرض میکنیم که یک دیتابیس به اسم phonebook حاوی یک جدول به‌اسم persons داریم که شامل فیلدهای id و name و phone هست که id کلید اصلی و عددیه و همچنین Auto Increment گذاشتیمش و name و phone هم از نوع متنی هستن و ضمناً name رو Unique کردیم تا تکراری قبول نکنه. دقت کنید که من زیاد وارد جزئیات سمت سرور نمیشم و کدها رو هم در ساده‌ترین حالت ممکن مینویسم و از شئ‌گرایی و مباحث امنیتی  در حد پایه و ساده استفاده شده و از وراثت و الگوهای طراحی و... هم استفاده نمیکنم (که البته باعث میشن کار اصولی‌تر و بهتر انجام بشه) چون تمرکزمون توی این آموزش، نوشتن کدهای سمت اندرویده. این کدی هست که برای وب‌سرویسمون مینویسیم:
<?php
 
$db = new mysqli('localhost', 'root', '', 'phonebook');
 
$result = ['code' => -1];
 
if (isset($_GET['action'])) {
    switch (strtolower($_GET['action'])) {
        case 'insert':
            if (isset($_GET['name'], $_GET['phone'])) {
                $result['code'] = 0;
                $name = $db->real_escape_string($_GET['name']);
                $phone = $db->real_escape_string($_GET['phone']);
                $db->query("INSERT INTO `persons` VALUES (NULL,'{$name}','{$phone}')");
                if ($db->affected_rows > 0) {
                    $result = [
                        'code' => 1,
                        'data' => $db->insert_id,
                    ];
                }
            }
            break;
        case 'search':
            if (isset($_GET['name'])) {
                $result['code'] = 0;
                $name = $db->real_escape_string($_GET['name']);
                $data = [];
                $rows = $db->query("SELECT * FROM `persons` WHERE (`name` LIKE '%{$name}%')");
                if ($rows && $rows->num_rows > 0) {
                    while ($row = $rows->fetch_assoc()) {
                        $data[] = $row;
                    }
                    $rows->free();
                }
                if (count($data) > 0) {
                    $result = [
                        'code' => 1,
                        'data' => $data,
                    ];
                }
            }
            break;
    }
}

header('Content-Type: application/json');
echo json_encode($result);

الان این وب‌سرویس درصورتی که کاربر یکی از اکشن‌های insert یا search رو درخواست بده، وارد حالت پردازش میشه و درصورتی که عملیات موردنظر با موفقیت انجام بشه (مثلاً درج در اکشن insert یا پیداکردن شخص در اکشن search)، اونوقت نتیجه‌ی 1 رو در بخش code و جزئیات جانبی رو در بخش data برمیگردونه. اگه اکشن دیگری درخواست بشه یا کلاً اکشنی درخواست نشه هم نتیجه‌ی -1 در قسمت code میده. این خروجی هم درقالب JSON برگردونده میشه. فرض میکنیم که این کد رو به‌اسم index.php در مسیر ws توی سایت ncis.ir ذخیره کردیم (یعنی ncis.ir/ws/index.php). بنابراین میتونیم به این شکل باهاش کار کنیم (اطلاعات و نمونه‌های خروجی فقط مثال هستن) :
ncis.ir/ws/index.php?action=insert&name=ali&phone=12345
// Result: {code:1,data:1}

ncis.ir/ws/index.php?action=insert&name=reza&phone=67890
// Result: {code:1,data:2}

ncis.ir/ws/index.php?action=insert&name=ali&phone=11111
// Result: {code:0}

ncis.ir/ws/index.php?action=search&name=ali
// Result: {code:1,data:[{id:1,name:"ali",phone:"12345"}]}

ncis.ir/ws/index.php?action=search&name=a
// Result: {code:1,data:[{id:1,name:"ali",phone:"12345"},{id:2,name:"reza",phone:"67890"}]}

ncis.ir/ws/index.php?action=search&name=mohsen
// Result: {code:0}

ncis.ir/ws/index.php?action=update
// Result: {code:-1}

ncis.ir/ws/index.php
// Result: {code:-1}
تشکر شده توسط: meysam1366 , alirezaey , miladsss , Mostafa4hc
#3
ایجاد ساختار نگهداری داده‌های یک شخص در اندروید

برای این کار یه کلاس ساده مینویسیم:

public class StructPerson {
    public int id;
    public String name;
    public String phone;
}

همونطور که میبینید، این کلاس هیچ کار خاصی نمیکنه و فقط یک ظرف برای نگهداری اطلاعات اشخاص محسوب میشه.
تشکر شده توسط: meysam1366 , alirezaey , miladsss , Mostafa4hc
#4
کلاس اتصال به سرور

قبل از اینکه کد کلاس رو بنویسیم، باید توضیح بدم (البته احتمالاً میدونین) که باید مجوز دسترسی به اینترنت رو در فایل مانیفست پروژه تعریف کنید:
<uses-permission android:name="android.permission.INTERNET" />

public class Webservice {

   public static String read(HashMap<String, String> params) {
       String result = null;
       String urlString = "http://www.ncis.ir/ws/index.php?";
       if (!params.isEmpty()) {
           for (String key : params.keySet()) {
               urlString += key + "=" + Uri.encode(params.get(key)) + "&";
           }
       }
       urlString = urlString.substring(0, urlString.length() - 1);
       try {
           result = new SimpleAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, urlString).get();
       } catch (InterruptedException | ExecutionException e) {
           e.printStackTrace();
       }
       return result;
   }

   private static class SimpleAsyncTask extends AsyncTask<String, Void, String> {

   protected final String doInBackground(String... strings) {
       final String[] result = new String[]{null};
       try {
           URL url = new URL(strings[0]);
           HttpURLConnection connection = (HttpURLConnection) url.openConnection();
           connection.setDoInput(true);
           connection.setDoOutput(true);
           connection.setRequestMethod("GET");
           connection.setConnectTimeout(10000);
           connection.connect();
           InputStream inputStream = connection.getInputStream();
           result[0] = convertInputStreamToString(inputStream);
       } catch (IOException e) {
           e.printStackTrace();
       }
       return result[0];
   }

   private static String convertInputStreamToString(InputStream inputStream) {
       try {
           BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
           StringBuilder builder = new StringBuilder();

           String line;

           while ((line = reader.readLine()) != null) {
               builder.append(line);
           }

           return builder.toString();
       } catch (IOException e) {
           e.printStackTrace();
       }

       return null;
       }
   }
}

توی این کد دو بخش مهم داریم: یکی متد read که public static هست و پارامترها رو بصورت HashMap براش میفرستیم (ترکیب Key/Value) و یکی دیگه کلاس داخلی به‌اسم SimpleAsyncTask که وظیفه‌داره یک URL رو بصورت متن بگیره و جواب رو بصورت متن دوباره برگردونه (همونطور که میدونید، JSON درواقع یک رشته‌ی متنی با فرمت خاص هست). متد read هم برای اینکه جواب رو از سرور بخونه، از کلاس SimpleAsyncTask که نوشتیم استفاده میکنه و اون رو توی یک Thread جداگانه اجرا میکنه تا برنامه تا زمانی که قراره جواب از سرور دریافت بشه، هنگ نکنه. البته از اونجا که از متد get در انتهای فراخوانی کلاسمون استفاده کردیم، اون تردی از برنامه که این متد رو صدا زده، تا وقتی جواب بیاد هنگ میکنه و متوقف میمونه و برای همین باید خود این متد رو توی یک ترد دیگه بجز UI Thread صدا بزنیم که ما هم از کلاس‌های AsyncTask که برای انجام کارهای مختلف مینویسیم کمک میگیریم.

یه توضیح هم درمورد کلاس SimpleAsyncTask بدم که چطور داره از کلاس HttpURLConnection برای ارسال پارامترها و دریافت نتایج کمک میگیره. ابتدا یک شئ از این کلاس میسازیم با کمک آدرس دریافتی و بعد با دستور setDoInput و setDoOutput مشخص میکنیم که هم پارامتر ورودی داریم میفرستیم و هم جواب خروجی از این اتصال دریافت میکنیم. متد ارسال رو GET میگذاریم و تایم‌اوت رو هم روی 10 ثانیه تنظیم میکنیم تا اگه در این فاصله جواب نیومد دیگه بیخیال بشه. بعد با کمک متد private static خودمون یعنی convertInputStreamToString جریان داده‌های دریافتی از سرور رو تبدیل به رشته میکنیم و بعنوان نتیجه برمیگردونیم.
تشکر شده توسط: alirezaey , miladsss , Mostafa4hc
#5
کلاس دستورات

این کلاس درواقع یک ظرف برای نگهداری یکسری متد استاتیک هست که از کلاس اتصال به سرور که در مرحله‌ی قبلی نوشتیم کمک میگیره و پارامترها رو براش میفرسته و جوابها رو در قالبی که مدنظر ماست برمیگردونه:
public class Commands {

    public static boolean insert(StructPerson person) {
        boolean result = false;
        HashMap<String, String> params = new HashMap<>();
        params.put("action", "insert");
        params.put("name", person.name);
        params.put("phone", person.phone);
        String json = Webservice.read(params);
        if (json != null) {
            try {
                JSONObject obj = new JSONObject(json);
                int code = obj.getInt("code");
                result = (code == 1);
            } catch (JSONException | NullPointerException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    public static ArrayList<StructPerson> search(String name) {
        ArrayList<StructPerson> result = new ArrayList<>();
        HashMap<String, String> params = new HashMap<>();
        params.put("action", "search");
        params.put("name", name);
        String json = Webservice.read(params);
        if (json != null) {
            try {
                JSONObject obj = new JSONObject(json);
                int code = obj.getInt("code");
                if (code == 1) {
                    JSONArray data = obj.getJSONArray("data");
                    int len = data.length();
                    for (int i = 0; i < len; i++) {
                        JSONObject item = data.getJSONObject(i);
                        StructPerson person = new StructPerson();
                        person.id = item.getInt("id");
                        person.name = item.getString("name");
                        person.phone = item.getString("phone");
                        result.add(person);
                    }
                }
            } catch (JSONException | NullPointerException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

همونطور که میبینید، این کلاس دو متد داره (insert و search) یعنی همون اکشن‌های ما توی وب‌سرویس. اولی یه شئ از کلاس StructPerson میگیره و اون رو با کمک کلاس WebService درج میکنه و چک میکنه که آیا در جواب، کد 1 برگردونده میشه یا نه و نتیجه رو بصورت یه عبارت منطقی true/false برمیگردونه.

متد دوم یعنی search نیاز به توضیح بیشتری داره. این متد یه پارامتر String یعنی همون نام رو میگیره و جستجو رو انجام میده و خروجی JSON دریافتی رو تبدیل به آرایه‌ای از اشیاء StructPerson میکنه و برمیگردونه. اما چطور این تبدیل انجام میشه؟ بیایم با همدیگه یه نگاه دقیق‌تر به یکی از مثال‌هایی که قبلاً برای صدازدن وب‌سرویس نوشتیم بندازیم:
ncis.ir/ws/index.php?action=search&name=a
// Result: {code:1,data:[{id:1,name:"ali",phone:"12345"},{id:2,name:"reza",phone:"67890"}]}

اگه خروجی رو یکم خواناتر کنیم و از تورفتگی استفاده کنیم، به این نتیجه میرسیم:
{
    code:1,
    data:
    [
        {
            id:1,
            name:"ali",
            phone:"12345"
        },
        {
            id:2,
            name:"reza",
            phone:"67890"
        }
    ]
}

توی ساختار JSON هرجا { } دیدین یعنی JSONObject و هرجایی که [ ] مشاهده کردین به‌معنای JSONArray هست. بقیه‌ی مقادیر یا بصورت متنی هستن که با کوتیشن در بخش مقدار از ساختار key/value مشخص میشن و یا بصورت عددی و منطقی و... هستن که از روی نوع مقدارشون میشه تشخیص داد که چه شیئی (مثل int و...) رو باید براشون بکار برد.

خوب حالا ببینیم ما این نتیجه رو چطور تبدیل به یک آرایه از اشیاء StructPerson میکنیم. ابتدا چک کردیم که کد 1 برگردونده بشه یعنی براساس name ارسال‌شده به وب‌سرویس، اطلاعاتی توی جدول پیدا شده یا نه. حالا اگه پیدا شده بود، همونطور که مشخصه باید قسمت data رو با کمک getJSONArray بگیریم و توی یک شئ از نوع JSONArray ذخیره کنیم. بعد تعداد عناصرش رو با کمک متد length میخونیم و یه حلقه تشکیل میدیم و یکی‌یکی آیتم موردنظر رو استخراج میکنیم که این آیتم همونطور که توی خروجی مشخصه، باز یک JSONObject هست. بعدش یک شئ StructPerson جدید میسازیم و یکی‌یکی عناصر id و name و phone رو از داخل item استخراج میکنیم و توی فیلدهای مربوطه قرار میدیم و اون شئ StructPerson که الان اطلاعاتش با نتایج دریافتی از سرور پر شده رو به آرایه‌ی result اضافه میکنیم. درنهایت هم همین آرایه رو برمیگردونیم.
تشکر شده توسط: alirezaey , miladsss , Mostafa4hc
#6
کلاس‌های ارسال درخواست به سرور بصورت MultiThread

خوب تقریباً رسیدیم به قسمت‌های ساده‌ی کار. باید برای هر اکشنمون یک کلاس AsyncTask بسازیم که درخواست رو توی یک ترد دیگه (بجز Main Thread) ارسال کنه تا برنامه در زمانی که منتظر هستیم جواب از سرور بیاد، هنگ نکنه و با وضعیت Not Responding مواجه نشیم. کلاس‌ها رو به این شکل مینویسیم:

public class AsyncInsert extends AsyncTask<StructPerson, Void, Boolean> {

    @Override
    protected Boolean doInBackground(StructPerson... persons) {
        return Commands.insert(persons[0]);
    }

    @Override
    protected void onPostExecute(Boolean result) {
        super.onPostExecute(result);
        if (result) {
            Log.i("DBG", "Data Inserted");
        } else {
            Log.e("DBG", "An error occured.");
        }
    }
}

public class AsyncSearch extends AsyncTask<String, Void, ArrayList<StructPerson>> {

    @Override
    protected ArrayList<StructPerson> doInBackground(String... strings) {
        return Commands.search(strings[0]);
    }

    @Override
    protected void onPostExecute(ArrayList<StructPerson> result) {
        super.onPostExecute(result);
        if (result.size() > 0) {
            for (StructPerson person : result) {
                Log.i("DBG", "ID: " + person.id + " , Name: " + person.name + " , Phone: " + person.phone);
            }
        } else {
            Log.e("DBG", "No match found.");
        }
    }
}
تشکر شده توسط: alirezaey , miladsss , Mostafa4hc
#7
استفاده از کلاس‌های AsyncTask در برنامه

حالا از این کلاسها چطور استفاده میکنیم؟ خیلی ساده:
StructPerson person = new StructPerson();
person.name = "Alireza";
person.phone = "1234567890";
new AsyncInsert().execute(person);

new AsyncSearch().execute("a");

همونطور که مشخصه، توی onPostExecute میتونیم کارهایی که میخوایم بعد از دریافت نتایج انجام بشه رو بنویسیم. اگه نیاز هست نتایج رو جای دیگری از برنامه نشون بدیم یا هر کار دیگری روی عناصر ظاهری برنامه انجام بدیم، میتونیم به‌ازای هرکدوم از اونها یک فیلد به کلاس AsyncTask خودمون اضافه کنیم و اگه private بود براش Setter بنویسیم و درنهایت توی onPostExecute ازش استفاده کنیم. برای مثال اگه میخوایم نتایج جستجو بجای اینکه توی LogCat ظاهر بشن، توی یه TextView در برنامه به نمایش در بیان، میتونیم کلاس AsyncSearch خودمون رو اینطوری تغییر بدیم:
public class AsyncSearch extends AsyncTask<String, Void, ArrayList<StructPerson>> {
    private TextView txtResult;
 
    @Override
    protected Boolean doInBackground(String... strings) {
        return Commands.search(strings[0]);
    }
 
    @Override
    protected void onPostExecute(ArrayList<StructPerson> result) {
        super.onPostExecute(result);
        if (txtResult != null) {
            if (result.size() > 0) {
                String message = "";
                for (StructPerson person : result) {
                    message + "ID: " + person.id + " , Name: " + person.name + " , Phone: " + person.phone + "\n\n";
                }
                txtResult.setText(message);
            } else {
                txtResult.setText("No match found.");
            }
        }
    }

    public AsyncSearch setTxtResult(TextView txtResult) {
        this.txtResult = txtResult;
        return this;
    }
}

همونطور که میبینید، من خروجی Setter رو برای txtResult از نوع خود کلاس گذاشتم و شئ جاری رو برگردوندم تا بتونم از Method Chaining کمک بگیرم. حالا چطور توی برنامه از این کلاس استفاده کنیم؟ اینطوری:
TextView txtResult = (TextView) findViewById(R.id.txtResult);
new AsyncSearch().setTxtResult(txtResult).execute("a");
تشکر شده توسط: miladsss , alirezaey , Mostafa4hc
#8
اگه میخواین برنامه‌نویسی اندروید رو بصورت اصولی و از پایه همراه با جزئیات خیلی بیشتر، به همین سادگی و با توضیحاتی به همین کاملی و بصورت قدم به قدم یاد بگیرین، میتونین از پکیج آموزش مقدماتی اندروید که مدرسش خودم هستم و تمام موارد رو با جزئیات کامل از پایه (شامل خود زبان جاوا) تدریس کردم استفاده کنید: http://shop.ncis.ir/basic-android
تشکر شده توسط: miladsss , alirezaey
#9
برای سهولت در ارجاع و مقایسه‌ی کارهایی که خودتون انجام دادین با سورس‌کد تست‌شده، میتونین از فایل ضمیمه استفاده کنید.


فایل‌های پیوست
.zip   WebService DEMO.zip (اندازه 308.52 KB / تعداد دانلود: 57)
تشکر شده توسط: meysam1366 , masiha68 , saeidmo , Mostafa4hc




کاربران در حال بازدید این موضوع: 1 مهمان