ارسالها: 3,701
موضوعها: 140
تاریخ عضویت: اردیبهشت 1394
اعتبار:
134
تشکرها: 195
3447 بار تشکر شده در 2120 پست
با سلام، بنا به درخواست زیاد دوستان، تصمیم گرفتم درقالب یک تاپیک آموزشی، نحوهی کار با وبسرویس رو در اندروید توضیح بدم. البته برای اینکار از کلاسهای داخلی خود اندروید و بدون هرگونه کتابخانهی جانبی استفاده میکنیم تا این آموزش در پایهایترین سطح خودش باقی بمونه و برای همه قابل استفاده باشه.
برای مدیریت بهتر مطالب، هر بخش از کار رو درقالب یک پست جداگانه توضیح میدم.
ارسالها: 3,701
موضوعها: 140
تاریخ عضویت: اردیبهشت 1394
اعتبار:
134
تشکرها: 195
3447 بار تشکر شده در 2120 پست
ایجاد وبسرویس در سمت سرور
برای اینکار من از PHP استفاده میکنم که رواج بیشتری داره ولی هیچ تفاوتی نمیکنه که وبسرویستون رو با چی بسازین. یه وبسرویس ساده میسازیم که وقتی بهش وصل میشیم، میتونه دو تا کار انجام بده:
ثبت یک نام و شماره تلفن
جستجوی یک نام و پیداکردن شماره تلفن از دیتابیس
برای اینکار فرض میکنیم که یک دیتابیس به اسم 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). بنابراین میتونیم به این شکل باهاش کار کنیم (اطلاعات و نمونههای خروجی فقط مثال هستن) :
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 جریان دادههای دریافتی از سرور رو تبدیل به رشته میکنیم و بعنوان نتیجه برمیگردونیم.
ارسالها: 3,701
موضوعها: 140
تاریخ عضویت: اردیبهشت 1394
اعتبار:
134
تشکرها: 195
3447 بار تشکر شده در 2120 پست
کلاس دستورات
این کلاس درواقع یک ظرف برای نگهداری یکسری متد استاتیک هست که از کلاس اتصال به سرور که در مرحلهی قبلی نوشتیم کمک میگیره و پارامترها رو براش میفرسته و جوابها رو در قالبی که مدنظر ماست برمیگردونه:
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 میکنه و برمیگردونه. اما چطور این تبدیل انجام میشه؟ بیایم با همدیگه یه نگاه دقیقتر به یکی از مثالهایی که قبلاً برای صدازدن وبسرویس نوشتیم بندازیم:
توی ساختار JSON هرجا { } دیدین یعنی JSONObject و هرجایی که [ ] مشاهده کردین بهمعنای JSONArray هست. بقیهی مقادیر یا بصورت متنی هستن که با کوتیشن در بخش مقدار از ساختار key/value مشخص میشن و یا بصورت عددی و منطقی و... هستن که از روی نوع مقدارشون میشه تشخیص داد که چه شیئی (مثل int و...) رو باید براشون بکار برد.
خوب حالا ببینیم ما این نتیجه رو چطور تبدیل به یک آرایه از اشیاء StructPerson میکنیم. ابتدا چک کردیم که کد 1 برگردونده بشه یعنی براساس name ارسالشده به وبسرویس، اطلاعاتی توی جدول پیدا شده یا نه. حالا اگه پیدا شده بود، همونطور که مشخصه باید قسمت data رو با کمک getJSONArray بگیریم و توی یک شئ از نوع JSONArray ذخیره کنیم. بعد تعداد عناصرش رو با کمک متد length میخونیم و یه حلقه تشکیل میدیم و یکییکی آیتم موردنظر رو استخراج میکنیم که این آیتم همونطور که توی خروجی مشخصه، باز یک JSONObject هست. بعدش یک شئ StructPerson جدید میسازیم و یکییکی عناصر id و name و phone رو از داخل item استخراج میکنیم و توی فیلدهای مربوطه قرار میدیم و اون شئ StructPerson که الان اطلاعاتش با نتایج دریافتی از سرور پر شده رو به آرایهی result اضافه میکنیم. درنهایت هم همین آرایه رو برمیگردونیم.
ارسالها: 3,701
موضوعها: 140
تاریخ عضویت: اردیبهشت 1394
اعتبار:
134
تشکرها: 195
3447 بار تشکر شده در 2120 پست
کلاسهای ارسال درخواست به سرور بصورت MultiThread
خوب تقریباً رسیدیم به قسمتهای سادهی کار. باید برای هر اکشنمون یک کلاس AsyncTask بسازیم که درخواست رو توی یک ترد دیگه (بجز Main Thread) ارسال کنه تا برنامه در زمانی که منتظر هستیم جواب از سرور بیاد، هنگ نکنه و با وضعیت Not Responding مواجه نشیم. کلاسها رو به این شکل مینویسیم:
ارسالها: 3,701
موضوعها: 140
تاریخ عضویت: اردیبهشت 1394
اعتبار:
134
تشکرها: 195
3447 بار تشکر شده در 2120 پست
استفاده از کلاسهای 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 خودمون رو اینطوری تغییر بدیم:
همونطور که میبینید، من خروجی Setter رو برای txtResult از نوع خود کلاس گذاشتم و شئ جاری رو برگردوندم تا بتونم از Method Chaining کمک بگیرم. حالا چطور توی برنامه از این کلاس استفاده کنیم؟ اینطوری:
TextView txtResult = (TextView) findViewById(R.id.txtResult);
new AsyncSearch().setTxtResult(txtResult).execute("a");
ارسالها: 3,701
موضوعها: 140
تاریخ عضویت: اردیبهشت 1394
اعتبار:
134
تشکرها: 195
3447 بار تشکر شده در 2120 پست
اگه میخواین برنامهنویسی اندروید رو بصورت اصولی و از پایه همراه با جزئیات خیلی بیشتر، به همین سادگی و با توضیحاتی به همین کاملی و بصورت قدم به قدم یاد بگیرین، میتونین از پکیج آموزش مقدماتی اندروید که مدرسش خودم هستم و تمام موارد رو با جزئیات کامل از پایه (شامل خود زبان جاوا) تدریس کردم استفاده کنید: http://shop.ncis.ir/basic-android