LXF160:Android-программирование
|
|
|
» Программирование. С базами данных Android умеет работать и в облаках
Содержание |
Android:Базы данных в облаке
Джульетта Кемп показывает, как подключиться к облаку и заставить свое приложение взаимодействовать с сервером.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
В прошлом месяце мы работали с локальной базой данных Android. Ну, а если нужно связаться с удаленной базой данных? Есть несколько способов это сделать, но на нашем уроке мы рассмотрим вариант, при котором для общения с базой данных используются HTTP-запросы – это, пожалуй, самый простой способ. Этот вариант также является расширяемым: мы получаем информацию из базы данных, но с помощью тех же методов и API можно получить все, что готов предоставить web-сервер. Мы будем обрабатывать данные с помощью JSON и посмотрим, как делать это в фоне, чтобы основное приложение не зависало при ожидании подключения от сервера.
Этот код основан на коде для работы с локальной базой данных из предыдущей статьи; если вы ее не читали, возьмите полный код приложения с нашего диска.
Базовый запрос HTTP
Прежде чем начать писать код HTTP-запроса, приложению нужно предоставить права доступа к сети. Добавьте в манифест строку
<uses-permission android:name=”android.permission.INTERNET” />
Для выполнения HTTP-запроса существует два варианта API: HTTPClient и HttpURLConnection. Начиная с Android 2.3, Google советует использовать HttpURLConnection, но на практике многие еще предпочитают HTTPClient, и с ним легче получить советы или помощь. Чтобы продемонстрировать оба класса в действии, я воспользуюсь HTTPClient для написания простого метода получения данных с сервера, а HttpURLConnection — для простого метода отправки данных на сервер.
Разбор JSON
Для отправки данных туда и обратно вам понадобится формат хранения данных, который смогут понять и телефон, и web-сервер. Хороший вариант, которым мы здесь и воспользуемся – JSON, сокращение от “JavaScript Object Notation [Формат описания объектов JavaScript]”. Это легкий формат обмена данными, который понятен и для чтения/записи человеку, и для разбора/генерации компьютеру. Существует множество других форматов обмена данными, но JSON – хороший базовый формат для строковых данных, которые и хранятся в нашей базе.
Один JSONObject будет напоминать описание одного из заданий из таблицы заданий приложения ToDo:
{“_id”:1,”task”:”test task”, “duedate”:”2011-12-24”, “createdate”:1324660453756, “category_link”:2}
Прежде чем вступать во взаимодействие с настоящим сервером MySQL, рассмотрим простой пример на PHP, который воспроизводит эту строку вручную:
<?php
$output=array(
“_id” => 1,
“category” => ‘work’
); print(json_encode($output));
?>
Сохраните этот код на сервере в файле test.php, и теперь он готов ответить на запрос вашего телефона. Затем создайте новый класс Android в своем проекте RemoteTodoSync.java:
public class RemoteTodoSync extends Activity {
public static final String WEBADDRESS = “http://yourserver/test.php”;
private static final String TAG = “RemoteTodoSync”;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
retrieveData();
Intent i = new Intent();
setResult(RESULT_OK, i);
finish();
}
}
Добавьте его в AndroidManifest.xml, затем добавьте такие строки в ToDo.java для вызова Действия и обработки результата:
public void onCreate(Bundle savedInstanceState) {
...
startActivityForResult(new Intent(this.getBaseContext(),
RemoteTodoSync.class), SERVER_UPDATE_ID);
...
}
@Override protected void onActivityResult(int requestCode, int resultCode, Intent i) {
super.onActivityResult(requestCode, resultCode, i);
switch (requestCode) {
case SERVER_UPDATE_ID:
populateList();
default:
// do nothing; no other result expected
}
}
Метод retrieveData() класса RemoteTodoSync получит нашу тестовую строку (представляющую объект JSONObject) из файла test.php:
private void retrieveData() {
InputStream is = null;
try {
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(WEBADDRESS);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
is = entity.getContent();
} catch (IOException e) {
Log.e(TAG, “IOException “ + e.toString());
e.printStackTrace();
}
try {
BufferedReader reader = new BufferedReader
(new InputStreamReader(is, “iso-8859-1”), 8);
String line = null;
while ((line = reader.readLine()) != null) {
JSONObject jo = new JSONObject(line);
Log.v(TAG, “id is: “ + jo.getInt(TodoDatabaseProvider.ID));
Log.v(TAG, “task is: “ + jo.getString(TodoDatabaseProvider.TASK));
}
is.close();
} catch (Exception e) {
Log.e(TAG, “Exception: “ + e.toString());
e.printStackTrace();
}
}
В первом блоке try создается HttpClient, отправляется запрос на URL, заданный в WEBADDRESS, и результат этого запроса сохраняется в InputStream. Во втором блоке производится разбор этого результата; сейчас результат просто выводится в лог-файл без дополнительных действий.
Посмотрев на код, вы увидите, что на самом деле считывается строка (String), которая затем вновь превращается в JSONObject. Так ее проще разобрать, поскольку можно воспользоваться методами разбора JSONObject, а не заниматься этим вручную. Как иллюстрируют приведенные здесь примеры получения целых чисел и строк, методами getX() из объекта JSONObject можно получить значения любых типов.
InputStreamReader преобразует байты входного потока (is) в символы с помощью преобразователя символов. BufferedReader буферизует результат в группах по восемь символов. С помощью этого примера можно посмотреть, что происходит (и убедиться, что все классы инициализированы правильно). Однако данные, полученные от настоящего сервера MySQL, будут представлены в виде JSONArray, который требует несколько большего разбора и выглядит так (квадратные скобки ограничивают массив):
[{“_id”:1,”task”:”test task”, “duedate”:”2011-12-24”, “createdate”:1324660453756, “category_link”:2}
{“_id”:2,”task”:”new work task”, “duedate”:”2011-12-31”, “createdate”:1324661242417, “category_link”:1}]
Вот простой пример PHP-кода для запуска на web-сервере (обратите внимание, здесь подразумевается, что база MySQL уже создана и заполнена данными, иначе никаких данных из запроса вы не получите):
<?php mysql_connect(“host”, “user”, “password”); mysql_select_db(“todo”);
$output;
$q = mysql_query(“select * from tasks”); while($e = mysql_fetch_assoc($q)) {
$output[] = $e;
} print(json_encode($output)); mysql_close();
?>
Новый улучшенный вариант разбора JSON выглядит так (первый блок try HttpClient остался без изменений):
try {
BufferedReader reader = new BufferedReader
(new InputStreamReader(is, “iso-8859-1”), 8);
String line = null;
while ((line = reader.readLine()) != null) {
JSONArray resultArray = new JSONArray(line);
for (int i = 0; i < resultArray.length(); i++) {
JSONObject jo = (JSONObject) resultArray.get(i);
Log.v(TAG, “id is: “ + jo.getInt(TodoDatabaseProvider.ID);
Log.v(TAG, “task is: “ + jo.getString(TodoDatabaseProvider.TASK));
}
}
is.close();
}
Сервер вернет одну строку для всего массива (хотя если он вернет несколько строк, наш код сумеет справиться с их обработкой), которую мы разбираем сначала в отдельные объекты JSONObject, а затем в необходимые компоненты. Опять же, возвращается строка, которая преобразуется в JSONArray – это упрощает разбор.
Работа с полученными данными
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Конечно, в том чтобы просто вывести данные в лог-файл, проку мало. Нам нужна какая-то форма синхронизации с базой данных Android. На этом уроке я не буду говорить о синхронизации (это довольно сложная тема – логика ее работы и проблемы, которые нужно обсудить, заняли бы большую часть статьи). Вместо этого мы выполним простую проверку на дубликаты и затем скопируем данные с сервера на устройство Android. В следующем разделе мы займемся отправкой данных с устройства Android на сервер, но по тем же причинам экономии места и внимания мы не будем касаться серверного кода, необходимого для их сброса в MySQL на стороне сервера.
При изменении кода локальной базы данных для работы с сервером метод retrieveData() во многом останется прежним. Однако JSONArray todoDataFromServer станет переменной класса, и мы не будем разбирать ее в компоненты JSONObject. Вместо этого мы добавим вызов addTodosFromServer() в onCreate() после вызова retrieveData(). Это большой фрагмент кода, и мы поместили его на DVD в Листинге 1.
addTodosFromServer() просто перебирает объекты JSONObject, возвращенные сервером в массиве JSONArray, проверяет, есть ли объект в локальной базе данных, методом checkIfTaskExists() и добавляет его в базу данных, если его там нет.
checkIfTaskExists() получает строку «задания» из JSONObject и выполняет SQL-запрос, который ищет это значение в локальной базе данных. Если запрос не возвращает результатов, задания не существует локально, и его нужно добавить.
Наконец, метод insertTask() добавляет новую строку в базу данных, получает новые значений из JSONObject и затем обновляет новый URI этими значениями. Используемые здесь методы insert() и update() рассматривались в предыдущей статье (если вы ее пропустили, их код можно найти на DVD).
Отправка данных на сервер
Мы узнали, как получить данные с сервера, но надо и знать, как их отправить. На стороне сервера мы просто запишем эти данные в файл. В реальном приложении их нужно будет добавить в базу данных MySQL, а также подумать о синхронизации данных, а не просто записать все данные в базу. На этот раз мы воспользуемся HttpUrlConnection вместо HttpClient.
Вот пример кода, который нужно разместить на сервере:
<?php
$myFile = “jsonout.txt”;
$fh = fopen($myFile, ‘w’) or die(“can’t open file”);
$json = file_get_contents(‘php://input’);
$obj = json_decode($json); fwrite($fh, “JSON var: “ . $json);
fclose($fh);
?>
Этот код выводит значения в указанный файл. Код метода addTodosToServer() для отправки данных на сервер слишком длинен, мы поместили его на LXFDVD. В первой части мы просто получаем текущие задания из локальной базы данных и преобразуем их в объекты JSONObject. Затем они помещаются в массив JSONArray taskArray, который будет отправлен на сервер. В последнем блоке try делается вся трудная работа: здесь мы создаем объект для взаимодействия с сервером HttpUrlConnection. При этом особенно важен метод setDoOutput() — без него HttpURLConnection не сможет ничего отправить. Метод setChunkedStreamingMode() позволяет отправлять данные порциями, а не все сразу. С небольшим объемом данных это не играет особой роли, но с большими может произойти переполнение, и такую практику стоит ввести в привычку. Мы также задаем метод запроса (здесь делать это не обязательно, так как по умолчанию используется метод GET; также поддерживаются POST, PUT и др.) и тип содержимого заголовка запроса [content-type], который определяет, какое содержимое мы передаем.
Для отправки данных нужно подключить DataOutputStream соединения HttpURLConnection и записать в него данные. После завершения записи важно очистить и закрыть его – и для того, чтобы гарантировать запись данных в поток, и для того, чтобы удалить все ненужные объекты. Наконец, обычно стоит получить статус запроса (это код возврата HTTP – 200 означает, что все прошло успешно, 404 – «страница не найдена» и т. д.) и записать его в лог-файл или обработать иным способом. После этого отключите HttpURLConnection.
Теперь наше приложение может связываться и обмениваться данными с удаленным web-сервером в обоих направлениях. Как вы убедились, два способа реализации такого взаимодействия, HttpClient и HttpUrlConnection, похожи; можно выбрать любой, но в долгосрочной перспективе стоит предпочесть HttpUrlConnection, поскольку, как мы упоминали выше, HttpClient устарел. Наш код будет запускаться автоматически при запуске приложения. На DVD можно найти код, в котором эти же действия привязаны к пункту меню (это удобно при тестировании).
Асинхронные задачи
При запуске приложения Android система создает «главную» нить выполнения, которая отвечает за взаимодействие с виджетами и другими компонентами пользовательского интерфейса Android. Все компоненты в этом процессе запускаются в одной и той же нити, и если не делать ничего особенного по части нитей, все остальное тоже будет выполняться в этой нити. Недостаток такого подхода состоит в том, что если взаимодействие занимает долгое время, пользователь может счесть приложение зависшим.
Все, что мы делаем здесь, выполняется в главной нити пользовательского интерфейса Действия. Это несложно с точки зрения программирования, но не слишком хорошо с точки зрения пользователя. Если приложению нужно много времени на подключение к серверу, оно затормозит. В лучшем случае это просто нервирует пользователя; в худшем, если приложение будет молчать достаточно долго, пользователь может не дождаться своего счастья и принудительно закрыть приложение. Чтобы обойти эту проблему, нам нужна другая нить для выполнения операций по работе с базой данных. Говоря о нитях, важно помнить, что все операции с графическим интерфейсом должны выполняться только в главной нити пользовательского интерфейса, так как инструментарий пользовательского интерфейса Android не является thread-safe (то есть не гарантирует безопасной работы при одновременном выполнении в нескольких нитях). И вся работа с кодом инструментария должна производиться в «главной» нити пользовательского интерфейса, и любые изменения в графическом интерфейсе (например, повторное заполнение списка после синхронизации с базой данных) должны выполняться в главной нити, а не в нашей второй нити.
В нашем приложении вся работа с базой данных будет осуществляться во второй нити, а затем мы вернемся в первую нить для обновления списка заданий. Самый простой вариант здесь – воспользоваться классом AsyncTask, предназначенным для упрощения подобной работы с нитями. Чтобы воспользоваться AsyncTask, нужно создать класс, унаследованный от него. Можно было бы создать внутренний класс в RemoteTodoSync, но это означало бы передачу выполнения нити из главного класса ToDo в RemoteTodoSync, что в свою очередь прервало бы взаимодействие с пользователем. Мы оставим RemoteTodoSync и создадим частный внутренний класс в ToDo.java, переместив все приватные методы RemoteTodoSync, которые и делают всю работу, в этот класс:
private class SyncDataTask extends AsyncTask<String, Void, Integer> {
@Override
protected void onPreExecute() {
// not doing anything here yet
}
@Override
protected Integer doInBackground(String... address) {
getAddress = address[0];
putAddress = address[1];
getData();
addTodosFromServer(taskArray);
updateIDsOnServer();
getLocalTasks();
sendTasksToRemoteDb();
return RESULT_OK;
}
@Override
protected void onPostExecute(Integer result) {
if (result == RESULT_OK) {
populateList();
}
}
В приведенном примере показаны три главных метода, которые вы, возможно, захотите реализовать для своего AsyncTask: onPreExecute(), doInBackground() и onPostExecute(). Основные действия выполняются в методе doInBackground().
В первой строке класса нужно определить шаблонные типы AsyncTask: здесь это <String, Void, Integer>. Первый аргумент – параметры, передаваемые AsyncTask при его вызове (см. код ниже), второй – тип единиц измерения хода процесса при выполнении в фоновом режиме, а третий – тип результата, возвращаемого методом __doInBackground(). Это возвращаемое значение будет передано в метод onPostExecute().
В данном случае метод doInBackground() получает адреса наших двух серверов: одного для получения данных, второго – для отправки, и вызывает уже написанные нами методы для взаимодействия с сервером. Все эти методы были перемещены в класс SyncDataTask, но они почти не изменились с прошлого раза (при необходимости загляните в код на DVD). Завершив свою работу, метод передает глобальную константу RESULT_OK__ методу onPostExecute(), который прибирается за задачей и возвращает соответствующее Намерение исходному вызывающему классу Todo (после получения Намерения список будет заполнены новыми задачами, полученными от сервера). Для инициализации AsyncTask мы добавляем две константы в верхнюю часть класса ToDo и строку в onCreate() для вызова задачи:
public static final String GETADDRESS = “http://yourserver/test.php”; public static final String PUTADDRESS = “http://10.0.0.4/test2.php”;
public void onCreate(Bundle savedInstanceState) {
...
new SyncDataTask().execute(GETADDRESS,PUTADDRESS);
...
}
Обратите внимание на две строки, которые передаются в SyncDataTask, и на то, что для вызова трех методов по порядку вы пользуетесь SyncDataTask().execute().
Теперь все взаимодействие с сервером осуществляется в отдельной нити, а приложение может сосредоточиться на взаимодействии с пользователем.
Индикатор выполнения процесса
У вас может возникнуть желание сообщать пользователю о том, что приложение делает в фоновом режиме. В AsyncTask есть удобный метод для отправки информации о ходе процесса обратно в главную нить – onProgressUpdate(). Чтобы им воспользоваться, сначала нужно убедиться, что второй аргумент шаблонного типа AsyncTask был задан верно. Мы воспользуемся CharSequence для отображения в окне хода выполнения процесса. Метод onProgressUpdate() выглядит так:
private final ProgressDialog dialog = new
ProgressDialog(ToDo.this);
@Override protected void onProgressUpdate(CharSequence... message) {
dialog.setMessage(message[0]);
dialog.show();
}
Чтобы воспользоваться этим, добавьте в любом месте класса, где должно появиться сообщение, вызов publishProgress (“сообщение”);. Диалог также нужно будет закрыть методом dialog.dismiss() в onPostExecute().
Однако при запуске этой версии вы заметите одну проблему, связанную с этим окном: оно будет мешать задаче полностью работать в фоне, так как фокус перейдет на окно сообщения. Это может быть удобно, если вы не хотите, чтобы пользователи работали с приложением при выполнении задачи в фоне – так они видят, что приложение не зависло.
Если вы хотите, чтобы пользователи могли работать с приложением, есть другой вариант – показать индикатор выполнения процесса в заголовке окна. Это просто. Для начала добавьте эту строку в метод onCreate() в ToDo.java (перед вызовом setContentView):
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
Получите настройки диалогового окна в SyncDataTask и добавьте в onPreExecute() строку
ToDo.this.setProgressBarIndeterminateVisibility(true);
а в onPostExecute() – строку
ToDo.this.setProgressBarIndeterminateVisibility(false);
(здесь не используется метод onProgressUpdate(), так как мы не обновляем информацию о ходе выполнения процесса, а просто показываем, что дела идут). Готово. Скомпилируйте и запустите программу, и вы увидите индикатор выполнения процесса в правом верхнем углу. Следующее, о чем нужно подумать, если вы хотите продолжить разработку приложения – что произойдет, если действие будет завершено или перезапущено в процессе выполнения? Это особенно актуально с AsyncTask, так как теперь у нас несколько нитей. |