Журнал LinuxFormat - перейти на главную

LXF160:Android-про­грам­ми­ро­ва­ние

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
 
(не показаны 5 промежуточных версий 1 участника)
Строка 1: Строка 1:
 
[[Категория:Постояные рубрики]]
 
[[Категория:Постояные рубрики]]
[[Категория:Android]
+
[[Категория:Android]]
  
 
'''» Про­грам­ми­ро­ва­ние. С базами данных Android умеет работать и в облаках'''
 
'''» Про­грам­ми­ро­ва­ние. С базами данных Android умеет работать и в облаках'''
Строка 90: Строка 90:
  
 
switch (requestCode) {
 
switch (requestCode) {
[[Файл:LXF160.code_android.rd_opt.jpeg |left |300px |thumb|> При­ло­же­ние на ре­аль­ном уст­рой­ст­ве.]]
+
[[Файл:LXF160.code_android.rd_opt.jpeg |right|300px |thumb|> При­ло­же­ние на ре­аль­ном уст­рой­ст­ве.]]
 
case SERVER_UPDATE_ID:
 
case SERVER_UPDATE_ID:
  
Строка 216: Строка 216:
  
 
===Ра­бо­та с по­лу­чен­ны­ми дан­ны­ми===
 
===Ра­бо­та с по­лу­чен­ны­ми дан­ны­ми===
 +
 +
{{Врезка|left|Заголовок=Скорая помощь |Ширина=20%|Содержание= Это не луч­ший спо­соб про­вер­ки дуб­ли­ка­тов. В иде­аль­ном слу­чае нуж­но поль­зо­вать­ся уни­каль­ны­ми иден­ти­фи­ка­то­ра­ми, но они долж­ны быть уни­каль­ны­ми и на ло­каль­ном, и на уда­лен­ном сер­ве­рах. Здесь мы поль­зу­ем­ся про­стей­шим ва­ри­ан­том, чис­то что­бы про­ил­лю­ст­ри­ро­вать идею.
 +
}}
  
 
Конеч­но, в том что­бы про­сто вы­вес­ти дан­ные в лог-файл, про­ку ма­ло. Нам нуж­на ка­кая-то фор­ма син­хрониза­ции с ба­зой дан­ных Android. На этом уро­ке я не бу­ду го­во­рить о син­хрониза­ции (это до­воль­но слож­ная те­ма – ло­ги­ка ее ра­бо­ты и про­бле­мы, ко­то­рые нуж­но об­су­дить, за­ня­ли бы боль­шую часть ста­тьи). Вме­сто это­го мы вы­полним про­стую про­вер­ку на дуб­ли­ка­ты и за­тем ско­пи­ру­ем дан­ные с сер­ве­ра на уст­рой­ст­во Android. В сле­дую­щем раз­де­ле мы зай­мем­ся от­прав­кой дан­ных с уст­рой­ст­ва Android на сер­вер, но по тем же при­чи­нам эко­но­мии мес­та и внимания мы не бу­дем ка­сать­ся сер­вер­но­го ко­да, необ­хо­ди­мо­го для их сбро­са в MySQL на сто­роне сер­ве­ра.
 
Конеч­но, в том что­бы про­сто вы­вес­ти дан­ные в лог-файл, про­ку ма­ло. Нам нуж­на ка­кая-то фор­ма син­хрониза­ции с ба­зой дан­ных Android. На этом уро­ке я не бу­ду го­во­рить о син­хрониза­ции (это до­воль­но слож­ная те­ма – ло­ги­ка ее ра­бо­ты и про­бле­мы, ко­то­рые нуж­но об­су­дить, за­ня­ли бы боль­шую часть ста­тьи). Вме­сто это­го мы вы­полним про­стую про­вер­ку на дуб­ли­ка­ты и за­тем ско­пи­ру­ем дан­ные с сер­ве­ра на уст­рой­ст­во Android. В сле­дую­щем раз­де­ле мы зай­мем­ся от­прав­кой дан­ных с уст­рой­ст­ва Android на сер­вер, но по тем же при­чи­нам эко­но­мии мес­та и внимания мы не бу­дем ка­сать­ся сер­вер­но­го ко­да, необ­хо­ди­мо­го для их сбро­са в MySQL на сто­роне сер­ве­ра.
Строка 226: Строка 229:
  
 
На­конец, ме­тод insertTask() до­бав­ля­ет но­вую стро­ку в ба­зу дан­ных, по­лу­ча­ет но­вые зна­чений из JSONObject и за­тем об­нов­ля­ет но­вый URI эти­ми зна­чения­ми. Ис­поль­зуе­мые здесь ме­то­ды insert() и update() рас­смат­ри­ва­лись в пре­ды­ду­щей ста­тье (ес­ли вы ее про­пусти­ли, их код мож­но най­ти на DVD).
 
На­конец, ме­тод 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().
 +
 +
Те­перь все взаи­мо­дей­ст­вие с сер­ве­ром осу­ще­ст­в­ля­ет­ся в от­дель­ной нити, а при­ло­жение мо­жет со­сре­до­то­чить­ся на взаи­мо­дей­ст­вии с поль­зо­ва­те­лем.
 +
[[Файл: LXF160.code_android.r_opt1.jpeg|left |400px |thumb|> Не ос­тав­ляй­те сво­их поль­зо­ва­те­лей во мра­ке — при­ят­ный ин­ди­ка­тор вы­пол­не­ния про­цес­са со­об­щит им, что де­ла­ет при­ло­же­ние.]]
 +
==Ин­ди­ка­тор вы­полнения про­цес­са==
 +
 +
У вас мо­жет возник­нуть же­лание со­об­щать поль­зо­ва­те­лю о том, что при­ло­жение де­ла­ет в фо­но­вом ре­жи­ме. В 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, так как те­перь у нас несколь­ко нитей. |

Текущая версия на 11:05, 29 сентября 2018


» Про­грам­ми­ро­ва­ние. С базами данных Android умеет работать и в облаках

Содержание

[править] Android:Ба­зы дан­ных в об­ла­ке

Джуль­ет­та Кемп по­ка­зы­ва­ет, как под­клю­чить­ся к об­ла­ку и за­ста­вить свое при­ло­жение взаи­мо­дей­ст­во­вать с сер­ве­ром.

LXF160.code android.expert.png


В про­шлом ме­ся­це мы ра­бо­та­ли с локаль­ной ба­зой дан­ных 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) {

(thumbnail)
> При­ло­же­ние на ре­аль­ном уст­рой­ст­ве.

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 – это уп­ро­ща­ет раз­бор.

[править] Ра­бо­та с по­лу­чен­ны­ми дан­ны­ми

Конеч­но, в том что­бы про­сто вы­вес­ти дан­ные в лог-файл, про­ку ма­ло. Нам нуж­на ка­кая-то фор­ма син­хрониза­ции с ба­зой дан­ных 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().

Те­перь все взаи­мо­дей­ст­вие с сер­ве­ром осу­ще­ст­в­ля­ет­ся в от­дель­ной нити, а при­ло­жение мо­жет со­сре­до­то­чить­ся на взаи­мо­дей­ст­вии с поль­зо­ва­те­лем.

(thumbnail)
> Не ос­тав­ляй­те сво­их поль­зо­ва­те­лей во мра­ке — при­ят­ный ин­ди­ка­тор вы­пол­не­ния про­цес­са со­об­щит им, что де­ла­ет при­ло­же­ние.

[править] Ин­ди­ка­тор вы­полнения про­цес­са

У вас мо­жет возник­нуть же­лание со­об­щать поль­зо­ва­те­лю о том, что при­ло­жение де­ла­ет в фо­но­вом ре­жи­ме. В 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, так как те­перь у нас несколь­ко нитей. |

Персональные инструменты
купить
подписаться
Яндекс.Метрика