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

LXF159:Android:Ба­зы дан­ных

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(На­строй­ка ба­зы дан­ных)
 
(не показаны 15 промежуточных версий 1 участника)
Строка 6: Строка 6:
  
 
=Android:Ба­зы дан­ных =
 
=Android:Ба­зы дан­ных =
''Джуль­ет­та Кемп хо­чет всего лишь соз­дать спи­сок за­дач, ко­то­рый сам бы эти за­да­чи и ре­шал. К со­жа­ле­нию, та­ко­го при­ло­же­ния еще нет.''
 
 
<span style="color:black"><span style="font-size: 300%"> Android под­дер­жи­ва­ет ба­зы дан­ных SQLite. Уз­най­те от Джуль­­ет­­ты Кемп, как соз­да­вать ба­зы дан­ных и по­стро­ить с ними спи­сок дел, раз­би­тый на ка­те­го­рии.  </span></span>
 
  
 +
''Android под­дер­жи­ва­ет ба­зы дан­ных SQLite. Уз­най­те от Джуль­­ет­­ты Кемп, как соз­да­вать ба­зы дан­ных и по­стро­ить с ними спи­сок дел, раз­би­тый на ка­те­го­рии.
 +
''
  
 
{{Врезка|left|Заголовок= Наш
 
{{Врезка|left|Заголовок= Наш
Строка 95: Строка 94:
  
 
}
 
}
{{Врезка|left|Заголовок= Об­нов­ле­ние ба­зы дан­ных|Ширина=80%|Содержание= Ес­ли ба­за дан­ных из­менит­ся, вы долж­ны иметь воз­мож­ность об­но­вить ее. Для это­го в верхней час­ти клас­са ContentProvider нуж­но за­дать но­мер вер­сии и на­пи­сать ме­тод onUpgrade() клас­са OpenHelper. (Об­ра­ти­те внимание, что к кон­стан­те DB_VERSION об­ра­ща­ет­ся глав­ный ме­тод клас­са OpenHelper).
+
{{Врезка|left|Заголовок= Об­нов­ле­ние ба­зы дан­ных|Ширина=40%|Содержание= Ес­ли ба­за дан­ных из­менит­ся, вы долж­ны иметь воз­мож­ность об­но­вить ее. Для это­го в верхней час­ти клас­са ContentProvider нуж­но за­дать но­мер вер­сии и на­пи­сать ме­тод onUpgrade() клас­са OpenHelper. (Об­ра­ти­те внимание, что к кон­стан­те DB_VERSION об­ра­ща­ет­ся глав­ный ме­тод клас­са OpenHelper).
  
 
private static final int DB_VERSION = 1;
 
private static final int DB_VERSION = 1;
Строка 116: Строка 115:
 
}}
 
}}
  
+
<pre>
 
@Override
 
@Override
  
Строка 153: Строка 152:
 
}
 
}
  
}
+
}  
 +
</pre>
  
 
Данный за­прос так­же соз­да­ет две за­пи­си в таб­ли­це Category. По­скольку это ContentProvider, ID дол­жен иметь суф­фикс “_id” в обе­их таб­ли­цах; в про­тив­ном слу­чае SQL вы­даст ошиб­ку. Такое происходит в свя­зи с осо­бен­но­стя­ми ContentProvider, а не ча­ст­ных баз дан­ных в Android. После этого реа­ли­зуй­те ме­то­ды onCreate() и query() клас­са ContentProvider в на­шем клас­се TodoDatabaseProvider:
 
Данный за­прос так­же соз­да­ет две за­пи­си в таб­ли­це Category. По­скольку это ContentProvider, ID дол­жен иметь суф­фикс “_id” в обе­их таб­ли­цах; в про­тив­ном слу­чае SQL вы­даст ошиб­ку. Такое происходит в свя­зи с осо­бен­но­стя­ми ContentProvider, а не ча­ст­ных баз дан­ных в Android. После этого реа­ли­зуй­те ме­то­ды onCreate() и query() клас­са ContentProvider в на­шем клас­се TodoDatabaseProvider:
Строка 221: Строка 221:
 
}
 
}
  
UriMatcher об­ра­ба­ты­ва­ет уникаль­ные иден­ти­фи­ка­то­ры ре­сур­сов и фор­ми­ру­ет це­лое чис­ло, в соответствии с кон­крет­ны­м ти­пом URI. Он по­ме­ча­ет URI из Authority и путь за­дан­ным це­лым чис­лом.
+
'''UriMatcher''' об­ра­ба­ты­ва­ет уникаль­ные иден­ти­фи­ка­то­ры ре­сур­сов и фор­ми­ру­ет це­лое чис­ло, в соответствии с кон­крет­ны­м ти­пом URI. Он по­ме­ча­ет URI из Authority и путь за­дан­ным це­лым чис­лом.
  
 
В этом слу­чае мы со­постав­ля­ем URI ви­да “com.example.todo.TodoDatabaseProvider/todo” (ко­то­рые, сле­до­ва­тель­но, ссы­ла­ют­ся на весь спи­сок) с це­лым чис­лом 100, а URI ви­да “com.example.todo.TodoDatabaseProvider/todo/1” (ко­то­рые, сле­до­ва­тель­но, ссы­ла­ют­ся на от­дель­ный эле­мент todo) с це­лым чис­лом 101. Это оз­на­ча­ет, что за­тем мы смо­жем об­ра­бо­тать их долж­ным об­ра­зом с по­мо­щью опе­ра­то­ров switch в ме­то­де query().
 
В этом слу­чае мы со­постав­ля­ем URI ви­да “com.example.todo.TodoDatabaseProvider/todo” (ко­то­рые, сле­до­ва­тель­но, ссы­ла­ют­ся на весь спи­сок) с це­лым чис­лом 100, а URI ви­да “com.example.todo.TodoDatabaseProvider/todo/1” (ко­то­рые, сле­до­ва­тель­но, ссы­ла­ют­ся на от­дель­ный эле­мент todo) с це­лым чис­лом 101. Это оз­на­ча­ет, что за­тем мы смо­жем об­ра­бо­тать их долж­ным об­ра­зом с по­мо­щью опе­ра­то­ров switch в ме­то­де query().
Строка 227: Строка 227:
 
Ес­ли URI со­от­вет­ст­ву­ет от­дель­но­му за­данию, мы до­бав­ля­ем к SQL-за­про­су вы­ра­жение where, ко­то­рое осу­ще­ст­в­ля­ет по­иск по усло­вию ‘id=#’; в про­тив­ном слу­чае вы­ра­жения where нет, и мы за­пра­ши­ва­ем весь спи­сок за­дач.
 
Ес­ли URI со­от­вет­ст­ву­ет от­дель­но­му за­данию, мы до­бав­ля­ем к SQL-за­про­су вы­ра­жение where, ко­то­рое осу­ще­ст­в­ля­ет по­иск по усло­вию ‘id=#’; в про­тив­ном слу­чае вы­ра­жения where нет, и мы за­пра­ши­ва­ем весь спи­сок за­дач.
  
SQLiteQueryBuilder, как сле­ду­ет из на­звания, по­мо­га­ет стро­ить за­про­сы для SQLite. Мы ве­лим ему ис­поль­зо­вать таб­ли­цу Task, до­бав­ля­ем где необ­хо­ди­мо вы­ра­жение where, а за­тем генери­ру­ем Cursor из ре­зуль­та­тов SQL-за­про­са. setNotificationUri() ре­ги­ст­ри­ру­ет Cursor для от­сле­жи­вания лю­бых из­менений в со­дер­жи­мом за­дан­но­го URI.
+
'''SQLiteQueryBuilder''', как сле­ду­ет из на­звания, по­мо­га­ет стро­ить за­про­сы для SQLite. Мы ве­лим ему ис­поль­зо­вать таб­ли­цу Task, до­бав­ля­ем где необ­хо­ди­мо вы­ра­жение where, а за­тем генери­ру­ем Cursor из ре­зуль­та­тов SQL-за­про­са. setNotificationUri() ре­ги­ст­ри­ру­ет Cursor для от­сле­жи­вания лю­бых из­менений в со­дер­жи­мом за­дан­но­го URI.
  
 
Нам так­же нуж­ны ме­то­ды insert() и update():
 
Нам так­же нуж­ны ме­то­ды insert() и update():
Строка 295: Строка 295:
 
update() про­ве­ря­ет URI, иден­ти­фи­ци­рую­щий от­дель­ную за­да­чу, и стро­ит вы­ра­жение where с иден­ти­фи­ка­то­ром за­да­чи. Мы опять же по­лу­ча­ем доступ­ную для за­пи­си ба­зу дан­ных и об­нов­ля­ем ее необ­хо­ди­мы­ми зна­чения­ми. Ре­зуль­та­том бу­дет ко­ли­че­­ст­во об­нов­лен­ных строк.
 
update() про­ве­ря­ет URI, иден­ти­фи­ци­рую­щий от­дель­ную за­да­чу, и стро­ит вы­ра­жение where с иден­ти­фи­ка­то­ром за­да­чи. Мы опять же по­лу­ча­ем доступ­ную для за­пи­си ба­зу дан­ных и об­нов­ля­ем ее необ­хо­ди­мы­ми зна­чения­ми. Ре­зуль­та­том бу­дет ко­ли­че­­ст­во об­нов­лен­ных строк.
  
==Об­нов­ле­ние ба­зы дан­ных==
+
==Поль­зо­ва­тель­ский ин­тер­фейс к ба­зе дан­ных==
  
Ес­ли ба­за дан­ных из­менит­ся, вы долж­ны иметь воз­мож­ность об­но­вить ее. Для это­го в верхней час­ти клас­са ContentProvider нуж­но за­дать но­мер вер­сии и на­пи­сать ме­тод onUpgrade() клас­са OpenHelper. (Об­ра­ти­те внимание, что к кон­стан­те DB_VERSION об­ра­ща­ет­ся глав­ный ме­тод клас­са OpenHelper).
+
{{Врезка|left|Заголовок= Скорая помощь|Ширина=10%|Содержание= Ес­ли вы раз­ра­ба­ты­вае­те API 11 для даль­ней­ше­го, ре­ко­мен­ду­ем ис­поль­зо­вать CursorLoader вме­сто ме­то­да managedQuery().}}
 +
[[Файл:LXF156.code_android.ldb_opt.png |right |400px | thumb|Спи­сок с пер­вым до­бав­лен­ным эле­мен­том. ]]
  
private static final int DB_VERSION = 1;
+
Мы соз­да­ли ба­зу дан­ных и ме­то­ды для досту­па к ней; те­перь нуж­но соз­дать ин­тер­фейс поль­зо­ва­те­ля для досту­па к ба­зе дан­ных. Как ука­за­но вы­ше, наш пер­во­на­чаль­ный ин­тер­фейс – спи­сок за­дач и пункт ме­ню для до­бав­ления но­во­го эле­мен­та спи­ска.
  
@Override
+
Мы уже в основ­ном с этим спра­ви­лись (ес­ли вам нуж­на по­мощь, оз­на­комь­тесь с ко­дом на DVD). Для по­лу­чения дан­ных из ба­зы и соз­дания спи­ска мы вы­зо­вем ме­тод populateList() из ме­то­да onCreate(). (Дан­ные для ка­те­го­рий мы по­ка не по­лу­ча­ем; зай­мем­ся этим поз­же).
  
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
private static final String[] TODO_PROJECTION = newString[] {
  
db.execSQL(“DROP TABLE IF EXISTS “ + TASK_TABLE);
+
TodoDatabaseProvider.ID,
  
db.execSQL(“DROP TABLE IF EXISTS “ + CATEGORY_TABLE);
+
TodoDatabaseProvider.COL_TASK,
  
onCreate(db);
+
TodoDatabaseProvider.COL_DUEDATE
  
}
+
};
  
В этой вер­сии не де­ла­ет­ся ниче­го осо­бен­но­го с об­нов­лением – про­сто уда­ля­ют­ся таб­ли­цы и сно­ва вы­зы­ва­ет­ся ме­тод onCreate(). В ре­аль­ном при­ло­жении нуж­но ре­шить, что вы собираетесь де­лать с ба­зой дан­ных и таб­ли­ца­ми при об­нов­лении, и соз­дать SQL-за­прос, ко­то­рый бу­дет вы­пол­нять соответствующие из­менения без по­те­ри дан­ных.
+
private void populateList() {
  
К со­жа­лению, сообщения об ошиб­ках SQL, за­пи­сы­вае­мые в лог-файл ddms, не слиш­ком ин­фор­ма­тив­ны. Ес­ли вы по­лу­чае­те ошиб­ку «Не­воз­мож­но об­но­вить ба­зу дан­ных толь­ко для чтения [cannot upgrade read only database]», наи­бо­лее ве­ро­ят­ное объ­яснение не име­ет ниче­го об­ще­го с недоступ­но­стью ба­зы дан­ных на запись – ско­рее все­го, вы про­сто обдернулись в SQL-за­про­се в ме­то­дах onCreate() или onUpgrade().
+
Cursor cursor = managedQuery(TodoDatabaseProvider.CONTENT_URI,
  
Про­верь­те в сво­ем ко­де пра­виль­ность рас­ста­нов­ки за­пя­тых и всякую дру­гую мел­очевку, и при необ­хо­ди­мо­сти по­про­буй­те вы­полнить за­прос вруч­ную с по­мо­щью sqlite3 (см. врез­ку «От­лад­ка ба­зы дан­ных»).
+
TODO_PROJECTION, null, null, null);
  
 +
String[] colNames = { TodoDatabaseProvider.COL_TASK,
  
{{Врезка|left|Заголовок= Скорая помощь|Ширина=10%|Содержание= Ес­ли вы раз­ра­ба­ты­вае­те API 11 для даль­ней­ше­го, ре­ко­мен­ду­ем ис­поль­зо­вать CursorLoader вме­сто ме­то­да managedQuery().
+
TodoDatabaseProvider.COL_DUEDATE };
}}  
+
  
==От­лад­ка ба­зы дан­ных==
+
int[] viewIDs = { R.id.taskname, R.id.duedate };
  
Ес­ли что-то пой­дет не так, у вас мо­жет возникнуть по­треб­ность взгля­нуть на свою ба­зу дан­ных и ра­зо­брать­ся, что про­ис­хо­дит и вер­ны ли дан­ные.
+
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
  
В SDK для Android вхо­дит ути­ли­та для ра­бо­ты с ба­зой дан­ных sqlite3, по­зво­ляющая про­смат­ри­вать со­дер­жи­мое таб­лиц и вы­пол­нять дру­гие дей­ст­вия из уда­лен­ной обо­лоч­ки. Вот при­мер под­клю­чения к ба­зе дан­ных (/data/data/packagename/databases/dbname.db – стан­дарт­ное рас­по­ло­жение баз дан­ных эму­ля­то­ра) и вы­во­да со­дер­жи­мо­го таб­ли­цы Tasks:
+
this,
  
adb -s emulator-5554 shell
+
R.layout.todo_item,
  
# sqlite3 /data/data/com.example.todo/databases/todo.db
+
cursor,
  
sqlite> .tables
+
colNames,
  
android_metadata categories tasks
+
viewIDs
 +
 
 +
); setListAdapter(adapter);
 +
 
 +
}
 +
 
 +
Всю черную ра­бо­ту вы­полнили за вас клас­сы Cursor и SimpleCursorAdapter (уч­ти­те: что­бы это ра­бо­та­ло ав­то­ма­ти­че­­ски, ва­ше За­ня­тие долж­но на­сле­до­вать ListActivity).
 +
{{Врезка|left|Заголовок=От­лад­ка ба­зы дан­ных |Ширина=100%|Содержание=
 +
Ес­ли что-то пой­дет не так, у вас мо­жет возникнуть по­треб­ность взгля­нуть на свою ба­зу дан­ных и ра­зо­брать­ся, что про­ис­хо­дит и вер­ны ли дан­ные.
 +
 
 +
В SDK для Android вхо­дит ути­ли­та для ра­бо­ты с ба­зой дан­ных sqlite3, по­зво­ляющая про­смат­ри­вать со­дер­жи­мое таб­лиц и вы­пол­нять дру­гие дей­ст­вия из уда­лен­ной обо­лоч­ки. Вот при­мер под­клю­чения к ба­зе дан­ных (/data/data/packagename/databases/dbname.db – стан­дарт­ное рас­по­ло­жение баз дан­ных эму­ля­то­ра) и вы­во­да со­дер­жи­мо­го таб­ли­цы Tasks:
  
sqlite> .dump tasks
+
adb -s emulator-5554 shell
 +
# sqlite3 /data/data/com.example.todo/databases/todo.db
 +
sqlite> .tables
 +
android_metadata categories tasks
 +
sqlite> .dump tasks
  
 
Ко­ман­ды SQLite на­чи­на­ют­ся с точ­ки и не тре­бу­ют точ­ки с за­пя­той на кон­це, а SQL-за­про­сы не на­чи­на­ют­ся с точ­ки и обяза­ны за­кан­чи­вать­ся точ­кой с за­­пя­­той:
 
Ко­ман­ды SQLite на­чи­на­ют­ся с точ­ки и не тре­бу­ют точ­ки с за­пя­той на кон­це, а SQL-за­про­сы не на­чи­на­ют­ся с точ­ки и обяза­ны за­кан­чи­вать­ся точ­кой с за­­пя­­той:
Строка 351: Строка 366:
 
sqlite> .exit
 
sqlite> .exit
  
Ес­ли вы вздумаете уда­ли­ть всю ба­зу дан­ных вруч­ную и впоследствии у вас поя­вят­ся про­бле­мы с ее по­втор­ным соз­да­ни­ем, тогда уда­ли­те свое при­ло­же­ние из эму­ля­то­ра и пе­ре­ус­та­но­ви­те его заново.
+
Ес­ли вы вздумаете уда­ли­ть всю ба­зу дан­ных вруч­ную и впоследствии у вас поя­вят­ся про­бле­мы с ее по­втор­ным соз­да­ни­ем, тогда уда­ли­те свое при­ло­же­ние из эму­ля­то­ра и пе­ре­ус­та­но­ви­те его заново. }}
 +
 +
{{Врезка|left|Заголовок= Соз­да­ние ме­ню с MenuInflater|Ширина=100%|Содержание=Есть не­сколь­ко спо­со­бов соз­да­ния ме­ню; здесь мы поль­зу­ем­ся MenuInflater, ге­не­ри­рую­щим ме­ню из XML-ко­да, так как удоб­но хра­нить боль­шую часть ин­тер­фей­са в XML. Ме­тод onCreateOptionsMenu() раз­ме­ща­ет­ся в ToDo.java; XML – в res/menu/todo_options_menu.xml, а API де­ла­ет всю ос­таль­ную ра­бо­ту.
  
Поль­зо­ва­тель­ский ин­тер­фейс к ба­зе дан­ных
+
@Override
  
Мы соз­да­ли ба­зу дан­ных и ме­то­ды для досту­па к ней; те­перь нуж­но соз­дать ин­тер­фейс поль­зо­ва­те­ля для досту­па к ба­зе дан­ных. Как ука­за­но вы­ше, наш пер­во­на­чаль­ный ин­тер­фейс – спи­сок за­дач и пункт ме­ню для до­бав­ления но­во­го эле­мен­та спи­ска.
+
public boolean onCreateOptionsMenu(Menu menu) {
  
Мы уже в основ­ном с этим спра­ви­лись (ес­ли вам нуж­на по­мощь, оз­на­комь­тесь с ко­дом на DVD). Для по­лу­чения дан­ных из ба­зы и соз­дания спи­ска мы вы­зо­вем ме­тод populateList() из ме­то­да onCreate(). (Дан­ные для ка­те­го­рий мы по­ка не по­лу­ча­ем; зай­мем­ся этим поз­же).
+
MenuInflater inflater = getMenuInflater();
  
private static final String[] TODO_PROJECTION = newString[] {
+
inflater.inflate(R.menu.todo_options_menu, menu);
  
TodoDatabaseProvider.ID,
+
return super.onCreateOptionsMenu(menu);
  
TodoDatabaseProvider.COL_TASK,
+
}
  
TodoDatabaseProvider.COL_DUEDATE
+
<?xml version=”1.0” encoding=”utf-8”?>
  
};
+
<menu xmlns:android=”http://schemas.android.com/apk/res/android”>
  
private void populateList() {
+
<item android:id=”@+id/menu_add”
  
Cursor cursor = managedQuery(TodoDatabaseProvider.CONTENT_URI,
+
android:title=”@string/menu_add”
  
TODO_PROJECTION, null, null, null);
+
android:alphabeticShortcut=’a’ />
  
String[] colNames = { TodoDatabaseProvider.COL_TASK,
+
</menu>
  
TodoDatabaseProvider.COL_DUEDATE };
+
На­ко­нец, ме­тод для об­ра­бот­ки щелч­ка по пунк­ту ме­ню:
  
int[] viewIDs = { R.id.taskname, R.id.duedate };
+
@Override
  
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
+
public boolean
  
this,
+
onContextItemSelected(MenuItem item) {
  
R.layout.todo_item,
+
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
  
cursor,
+
switch(item.getItemId()) {
  
colNames,
+
case R.id.menu_add:
  
viewIDs
+
Intent i = new Intent(Intent.ACTION_INSERT);
  
); setListAdapter(adapter);
+
i.setClass(this, TodoEditor.class);
 +
 
 +
startActivity(i);
  
}
+
return true;
 +
 
 +
default:
 +
 
 +
return super.onOptionsItemSelected(item);
 +
 
 +
} }}
 +
 
 +
Про­ек­ция оп­ре­де­ля­ет, ка­кие столб­цы за­про­са нуж­но вер­нуть. SimpleCursorAdapter по­лу­ча­ет cursor, мас­сив строк столб­цов, ко­то­рые нуж­но ото­бра­зить, и иден­ти­фи­ка­то­ры пред­став­лений viewed, ко­то­рым долж­ны со­от­вет­ст­во­вать дан­ные этих столб­цов. За­тем спи­сок чу­дес­ным об­ра­зом за­пол­ня­ет­ся.
 +
 
 +
Про­ек­ция оп­ре­де­ля­ет, ка­кие столб­цы за­про­са нуж­но вер­нуть. SimpleCursorAdapter по­лу­ча­ет cursor, мас­сив строк столб­цов, ко­то­рые нуж­но ото­бра­зить, и иден­ти­фи­ка­то­ры пред­став­лений viewed, ко­то­рым долж­ны со­от­вет­ст­во­вать дан­ные этих столб­цов. За­тем спи­сок чу­дес­ным об­ра­зом за­пол­ня­ет­ся.
  
Всю черную ра­бо­ту вы­полнили за вас клас­сы Cursor и SimpleCursorAdapter (уч­ти­те: что­бы это ра­бо­та­ло ав­то­ма­ти­че­­ски, ва­ше За­ня­тие долж­но на­сле­до­вать ListActivity). Про­ек­ция оп­ре­де­ля­ет, ка­кие столб­цы за­про­са нуж­но вер­нуть. SimpleCursorAdapter по­лу­ча­ет cursor, мас­сив строк столб­цов, ко­то­рые нуж­но ото­бра­зить, и иден­ти­фи­ка­то­ры пред­став­лений viewed, ко­то­рым долж­ны со­от­вет­ст­во­вать дан­ные этих столб­цов. За­тем спи­сок чу­дес­ным об­ра­зом за­пол­ня­ет­ся.
 
  
 
==До­бав­ление эле­мен­та==
 
==До­бав­ление эле­мен­та==
Строка 486: Строка 514:
  
 
Для это­го сна­ча­ла нуж­но на­пи­сать за­прос join, ко­то­рый ссы­ла­ет­ся на две таб­ли­цы. managedQuery() не под­дер­жи­ва­ет ра­бо­ту с несколь­ки­ми таб­ли­ца­ми, и нам при­дет­ся на­пи­сать спе­ци­аль­ный SQL-за­прос. Все это, а так­же код, ко­то­рый по­на­до­бит­ся вам для уда­ления и из­менения за­пи­сей в ба­зе дан­ных, мож­но най­ти на LXFDVD (или на www.linuxformat.com).
 
Для это­го сна­ча­ла нуж­но на­пи­сать за­прос join, ко­то­рый ссы­ла­ет­ся на две таб­ли­цы. managedQuery() не под­дер­жи­ва­ет ра­бо­ту с несколь­ки­ми таб­ли­ца­ми, и нам при­дет­ся на­пи­сать спе­ци­аль­ный SQL-за­прос. Все это, а так­же код, ко­то­рый по­на­до­бит­ся вам для уда­ления и из­менения за­пи­сей в ба­зе дан­ных, мож­но най­ти на LXFDVD (или на www.linuxformat.com).
 +
 +
==До­машнее за­дание==
 +
 +
Это при­ло­жение ра­бо­та­ет пре­крас­но, но оно до­воль­но про­стое. Вот несколь­ко ве­щей, ко­то­рые вы мо­же­те за­хо­теть по­про­бо­вать, что­бы луч­ше осво­ить ра­бо­ту API:
 +
 +
* Ка­те­го­рия не мо­жет быть пустой, ее нуж­но за­да­вать.
 +
* Поль­зо­ва­тель не мо­жет ре­дак­ти­ро­вать доступ­ные ка­те­го­рии. Для их об­ра­бот­ки и для кор­рект­ной ра­бо­ты с На­ме­рения­ми для вы­зо­ва кор­рект­ных За­ня­тий вы ско­рее все­го соз­да­ди­те но­вое За­ня­тие (ре­дак­ти­ро­вать спи­сок/ре­дак­ти­ро­вать ка­те­го­рию).
 +
* По­сле за­вер­шения за­да­чи ее мож­но толь­ко уда­лить. Как на­счет про­став­ления га­лоч­ки?
 +
* Что­бы да­ты со­хра­ня­лись кор­рект­но, их нуж­но вво­дить в спе­ци­аль­ном фор­ма­те. Вме­сто это­го хо­ро­шо бы по­до­шел ка­лен­дарь или дру­гая фор­ма вво­да.
 +
 +
Взгляните на вид­жет да­ты в Android.
 +
 +
В сле­дую­щей ста­тье этой се­рии мы по­смот­рим, как по­лу­чать ин­фор­ма­цию из уда­лен­ной ба­зы дан­ных и за­пи­сы­вать в нее дан­ные. |

Текущая версия на 14:07, 26 сентября 2018


Android.

Содержание

[править] Android:Ба­зы дан­ных

Android под­дер­жи­ва­ет ба­зы дан­ных SQLite. Уз­най­те от Джуль­­ет­­ты Кемп, как соз­да­вать ба­зы дан­ных и по­стро­ить с ними спи­сок дел, раз­би­тый на ка­те­го­рии.


По­ка в на­ших ста­тьях не за­тра­ги­ва­лось хранение дан­ных. Од­на­ко во мно­гих про­ек­тах оно по­на­до­бит­ся. В Android есть для это­го не один спо­соб, но для струк­ту­ри­ро­ван­ных или взаи­мо­свя­зан­ных дан­ных луч­ше по­дой­дет ба­за дан­ных.

Ес­ли дан­ные хра­нят­ся локаль­но, это оз­на­ча­ет, что ис­поль­зу­ет­ся встро­ен­ная под­держ­ка SQLite (в сле­дую­щей ста­тье мы по­го­во­рим о досту­пе к дан­ным на уда­лен­ных сер­ве­рах и о том, как встро­ить его в свое при­ло­жение).

Что­бы оп­ро­бо­вать это на прак­ти­ке, мы соз­да­дим спи­сок за­дач с дву­мя таб­ли­ца­ми (од­ной для за­дач и од­ной для ка­те­го­рий) и постараемся по­лу­чить пред­став­ление об управ­лении мно­же­ст­­вом таб­лиц и свя­зей.

[править] На­строй­ка про­ек­та и ба­зы дан­ных

Вос­поль­зу­ем­ся API уров­ня 10 (2.3.3):

android create project --target android-10 --name todo \\

--path ~/android/todo --activity ToDo --package com.example.todo

Пре­ж­де чем брать­ся за на­пи­сание ко­да, важ­но тща­тель­но про­ду­мать струк­ту­ру сво­ей ба­зы дан­ных, что­бы по­том не при­шлось тра­тить вре­мя на ис­прав­ления.

В на­шем про­ек­те понадобятся две таб­ли­цы (хо­тя сна­ча­ла мы бу­дем поль­зо­вать­ся толь­ко од­ной из них) – Tasks (за­да­чи) и Categories (ка­те­го­рии):

За­да­чи

  • ID (с ав­то­ин­кре­мен­том)
  • ID Опи­сание за­да­чи
  • ID Срок вы­полнения
  • ID Ссыл­ка на ка­те­го­рию

Ка­те­го­рии

  • ID (с ав­то­ин­кре­мен­том)
  • На­звание ка­те­го­рии

Для на­ше­го ин­тер­фей­са требует­ся пер­вич­ное За­ня­тие, ко­то­рое ото­бра­жа­ет спи­сок за­дач со сро­ка­ми вы­полнения и ка­те­го­рия­ми. Нам так­же по­на­до­бит­ся вто­рое За­ня­тие, ко­то­рое занима­ет­ся до­бав­лением и из­менением за­дач.

[править] На­строй­ка ба­зы дан­ных

Когда план на­шей ба­зы дан­ных го­тов, для его реа­ли­за­ции нам по­на­до­бит­ся класс TodoDatabaseProvider, ко­то­рый яв­ля­ет­ся на­следником ContentProvider и занима­ет­ся взаи­мо­дей­ст­ви­ем с ба­зой дан­ных. Стро­го го­во­ря, ContentProvider ну­жен толь­ко тогда, когда вы хо­ти­те де­лить­ся дан­ны­ми с дру­ги­ми при­ло­жения­ми, а не поль­зо­вать­ся ими толь­ко в од­ном. Од­на­ко с его по­мо­щью удоб­но де­лить­ся дан­ны­ми и внут­ри За­ня­тия; кро­ме то­го, он пре­достав­ля­ет вам раз­лич­ные вспо­мо­га­тель­ные ме­то­ды и ас­пек­ты API в ка­че­­ст­ве ин­тер­фей­са к ба­зе дан­ных SQLite.

Что­бы восполь­зо­вать­ся ContentProvider, ука­жи­те его уникаль­ный иден­ти­фи­ка­тор ре­сур­са (URI) в ви­де пуб­лич­ной кон­стан­ты в верхней час­ти клас­са:

public static final String AUTHORITY = “com.example.todo.tododatabaseprovider”; public static final String TODO_BASE_PATH = “todo”;

public static final Uri CONTENT_URI = Uri.parse(“content://” + AUTHORITY + “/” + TODO_BASE_PATH);

Так­же по­тре­бу­ет­ся за­ре­ги­ст­ри­ро­вать его в AndroidManifest.xml с пол­но­стью оп­ре­де­лен­ным име­нем клас­са ContentProvider:

<provider android:authorities=“com.example.todo.tododatabaseprovider”

android:multiprocess=“true”

android:name=”

com.example.todo.TodoDatabaseProvider”>

</provider>

В ат­ри­бу­те name нуж­но ука­зать пол­но­стью оп­ре­де­лен­ное имя клас­са ContentProvider. Ат­ри­бут authority дол­жен со­от­вет­ст­во­вать зна­чению, за­дан­но­му в ко­де, ко­то­рое оп­ре­де­ля­ет про­вай­де­ра, без ука­зания пу­ти. Что­бы оно бы­ло уникаль­ным, оно долж­но точ­но сов­па­дать с именем клас­са (но за­пи­сы­вать­ся толь­ко в нижнем ре­ги­ст­ре).

Для об­ра­бот­ки соз­дания ба­зы дан­ных мы восполь­зу­ем­ся внут­ренним вспо­мо­га­тель­ным клас­сом inner helper. Соз­дай­те при­ват­ный под­класс SQLiteOpenHelper и пе­ре­гру­зи­те ме­тод onCreate():

private static class TodoDBOpenHelper extends SQLiteOpenHelper {

TodoDBOpenHelper(Context c) {

super(c, DB_NAME, null, DB_VERSION);

}

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(“CREATE TABLE” + TASK_TABLE_NAME + “ (“

+ ID + “ INTEGER PRIMARY KEY,”

+ COL_TASK + “ TEXT,”

+ COL_DUEDATE + “ DATE,”

+ COL_CREATEDATE + “ INTEGER,”

COL_CATEGORY_LINK + “ INTEGER REFERENCES ”

+ CATEGORY_TABLE + “(”

+ COL_CATEGORY_ID + “)”

+ “);”);

db.execSQL(“CREATE TABLE” + CATEGORY_TABLE_NAME + “ (“

+ ID + “ INTEGER PRIMARY KEY,”

+ COL_CATEGORY + “ TEXT”

+ “);”);

db.execSQL(“INSERT INTO “ + CATEGORY_TABLE + “VALUES(1, ‘work’);”);

db.execSQL(“INSERT INTO “ + CATEGORY_TABLE + “VALUES(2, ‘personal’);”);

}

} 

Данный за­прос так­же соз­да­ет две за­пи­си в таб­ли­це Category. По­скольку это ContentProvider, ID дол­жен иметь суф­фикс “_id” в обе­их таб­ли­цах; в про­тив­ном слу­чае SQL вы­даст ошиб­ку. Такое происходит в свя­зи с осо­бен­но­стя­ми ContentProvider, а не ча­ст­ных баз дан­ных в Android. После этого реа­ли­зуй­те ме­то­ды onCreate() и query() клас­са ContentProvider в на­шем клас­се TodoDatabaseProvider:

private static final int TODOS = 100;

private static final int TODO_ID = 101;

private static final UriMatcher matcher =

new UriMatcher(UriMatcher.NO_MATCH);

static {

matcher.addURI(AUTHORITY, TODO_BASE_PATH, TODOS);

matcher.addURI(AUTHORITY, TODO_BASE_PATH + “/#”, TODO_ID);

}

@Override

public boolean onCreate() {

db = new TodoDBOpenHelper(getContext());

return true;

}

@Override

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

SQLiteQueryBuilder builder = new SQLiteQueryBuilder();

builder.setTables(TASK_TABLE);

int uriType = matcher.match(uri);

switch (uriType) {

case TODO_ID:

builder.appendWhere(ID + “=” + uri.getLastPathSegment());

break;

case TODOS:

break;

default:

throw new IllegalArgumentException(“Unknown URI” + uri);

}

Cursor cursor = builder.query(db.getReadableDatabase(),

projection, selection, selectionArgs, null, null, sortOrder);

cursor.setNotificationUri(getContext().getContentResolver(), uri);

return cursor;

}

UriMatcher об­ра­ба­ты­ва­ет уникаль­ные иден­ти­фи­ка­то­ры ре­сур­сов и фор­ми­ру­ет це­лое чис­ло, в соответствии с кон­крет­ны­м ти­пом URI. Он по­ме­ча­ет URI из Authority и путь за­дан­ным це­лым чис­лом.

В этом слу­чае мы со­постав­ля­ем URI ви­да “com.example.todo.TodoDatabaseProvider/todo” (ко­то­рые, сле­до­ва­тель­но, ссы­ла­ют­ся на весь спи­сок) с це­лым чис­лом 100, а URI ви­да “com.example.todo.TodoDatabaseProvider/todo/1” (ко­то­рые, сле­до­ва­тель­но, ссы­ла­ют­ся на от­дель­ный эле­мент todo) с це­лым чис­лом 101. Это оз­на­ча­ет, что за­тем мы смо­жем об­ра­бо­тать их долж­ным об­ра­зом с по­мо­щью опе­ра­то­ров switch в ме­то­де query().

Ес­ли URI со­от­вет­ст­ву­ет от­дель­но­му за­данию, мы до­бав­ля­ем к SQL-за­про­су вы­ра­жение where, ко­то­рое осу­ще­ст­в­ля­ет по­иск по усло­вию ‘id=#’; в про­тив­ном слу­чае вы­ра­жения where нет, и мы за­пра­ши­ва­ем весь спи­сок за­дач.

SQLiteQueryBuilder, как сле­ду­ет из на­звания, по­мо­га­ет стро­ить за­про­сы для SQLite. Мы ве­лим ему ис­поль­зо­вать таб­ли­цу Task, до­бав­ля­ем где необ­хо­ди­мо вы­ра­жение where, а за­тем генери­ру­ем Cursor из ре­зуль­та­тов SQL-за­про­са. setNotificationUri() ре­ги­ст­ри­ру­ет Cursor для от­сле­жи­вания лю­бых из­менений в со­дер­жи­мом за­дан­но­го URI.

Нам так­же нуж­ны ме­то­ды insert() и update():

@Override

public Uri insert(Uri uri, ContentValues values) {

int uriType = matcher.match(uri);

if (uriType != TODOS) {

throw new IllegalArgumentException(“Invalid URI for insert”);

}

SQLiteDatabase sqlDB = db.getWritableDatabase();

long newID = sqlDB.insert(TASK_TABLE, COL_TASK, values);

if (newID > 0) {

Uri newUri = ContentUris.withAppendedId(uri, newID);

getContext().getContentResolver().notifyChange(uri, null);

return newUri;

} else {

throw new SQLException(“Failed to insert row into “ + uri + “result “ + newID);

}

}

@Override

public int update(Uri uri, ContentValues values, String selection,

String[] selectionArgs) {

int uriType = matcher.match(uri);

if (uriType != TODO_ID) {

throw new IllegalArgumentException(“Invalid URI for update”);

}

long id = ContentUris.parseId(uri);

String where = ID + “=’” + id + “’”;

SQLiteDatabase sqlDB = db.getWritableDatabase();

int result = sqlDB.update(TASK_TABLE, values, where, null);

return result;

}

Ме­тод insert() про­ве­ря­ет URI, иден­ти­фи­ци­рую­щий всю таб­ли­цу (по­сколь­ку мы встав­ля­ем но­вую стро­ку, а не вы­би­ра­ем из су­ще­ст­вую­щих), по­лу­ча­ет доступ­ную для за­пи­си ба­зу дан­ных из при­ват­но­го эк­зем­п­ля­ра TodoDbOpenHelper (db) и за­тем про­бу­ет до­ба­вить дан­ные – Insert.

Пе­ре­мен­ная COL_TASK требуется нам по­то­му, что аб­со­лют­но пустую стро­ку SQL в таб­ли­цу до­ба­влять не станет. Ес­ли зна­чения values пусты (как обыч­но и происходит в на­шем слу­чае), в COL_TASK по­па­дет яв­ный NULL. Ес­ли воз­вра­щае­мое зна­чение нену­ле­вое, это иден­ти­фи­ка­тор вновь до­бав­лен­ной стро­ки; за­тем ме­тод notifyChange() опо­ве­ща­ет все за­ре­ги­ст­ри­ро­ван­ные ме­то­ды-на­блю­да­те­ли об об­нов­лении стро­ки. В про­тив­ном слу­чае ме­тод за­вер­ша­ет­ся неудач­но, и мы по­лу­ча­ем ис­клю­чение.

update() про­ве­ря­ет URI, иден­ти­фи­ци­рую­щий от­дель­ную за­да­чу, и стро­ит вы­ра­жение where с иден­ти­фи­ка­то­ром за­да­чи. Мы опять же по­лу­ча­ем доступ­ную для за­пи­си ба­зу дан­ных и об­нов­ля­ем ее необ­хо­ди­мы­ми зна­чения­ми. Ре­зуль­та­том бу­дет ко­ли­че­­ст­во об­нов­лен­ных строк.

[править] Поль­зо­ва­тель­ский ин­тер­фейс к ба­зе дан­ных

(thumbnail)
Спи­сок с пер­вым до­бав­лен­ным эле­мен­том.

Мы соз­да­ли ба­зу дан­ных и ме­то­ды для досту­па к ней; те­перь нуж­но соз­дать ин­тер­фейс поль­зо­ва­те­ля для досту­па к ба­зе дан­ных. Как ука­за­но вы­ше, наш пер­во­на­чаль­ный ин­тер­фейс – спи­сок за­дач и пункт ме­ню для до­бав­ления но­во­го эле­мен­та спи­ска.

Мы уже в основ­ном с этим спра­ви­лись (ес­ли вам нуж­на по­мощь, оз­на­комь­тесь с ко­дом на DVD). Для по­лу­чения дан­ных из ба­зы и соз­дания спи­ска мы вы­зо­вем ме­тод populateList() из ме­то­да onCreate(). (Дан­ные для ка­те­го­рий мы по­ка не по­лу­ча­ем; зай­мем­ся этим поз­же).

private static final String[] TODO_PROJECTION = newString[] {

TodoDatabaseProvider.ID,

TodoDatabaseProvider.COL_TASK,

TodoDatabaseProvider.COL_DUEDATE

};

private void populateList() {

Cursor cursor = managedQuery(TodoDatabaseProvider.CONTENT_URI,

TODO_PROJECTION, null, null, null);

String[] colNames = { TodoDatabaseProvider.COL_TASK,

TodoDatabaseProvider.COL_DUEDATE };

int[] viewIDs = { R.id.taskname, R.id.duedate };

SimpleCursorAdapter adapter = new SimpleCursorAdapter(

this,

R.layout.todo_item,

cursor,

colNames,

viewIDs

); setListAdapter(adapter);

}

Всю черную ра­бо­ту вы­полнили за вас клас­сы Cursor и SimpleCursorAdapter (уч­ти­те: что­бы это ра­бо­та­ло ав­то­ма­ти­че­­ски, ва­ше За­ня­тие долж­но на­сле­до­вать ListActivity).


Про­ек­ция оп­ре­де­ля­ет, ка­кие столб­цы за­про­са нуж­но вер­нуть. SimpleCursorAdapter по­лу­ча­ет cursor, мас­сив строк столб­цов, ко­то­рые нуж­но ото­бра­зить, и иден­ти­фи­ка­то­ры пред­став­лений viewed, ко­то­рым долж­ны со­от­вет­ст­во­вать дан­ные этих столб­цов. За­тем спи­сок чу­дес­ным об­ра­зом за­пол­ня­ет­ся.

Про­ек­ция оп­ре­де­ля­ет, ка­кие столб­цы за­про­са нуж­но вер­нуть. SimpleCursorAdapter по­лу­ча­ет cursor, мас­сив строк столб­цов, ко­то­рые нуж­но ото­бра­зить, и иден­ти­фи­ка­то­ры пред­став­лений viewed, ко­то­рым долж­ны со­от­вет­ст­во­вать дан­ные этих столб­цов. За­тем спи­сок чу­дес­ным об­ра­зом за­пол­ня­ет­ся.


[править] До­бав­ление эле­мен­та

Те­перь мы мо­жем ото­бра­зить спи­сок за­даний, но в нем ниче­го нет. Для до­бав­ления за­дания восполь­зу­ем­ся но­вым За­ня­ти­ем – TodoEditor:

@Override public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

final Intent intent = getIntent();

final String action = intent.getAction();

if (Intent.ACTION_INSERT.equals(action)) {

state = STATE_INSERT;

uri = getContentResolver().insert(TodoDatabaseProvider.

CONTENT_URI, null);

if (uri == null) {

Log.e(TAG, “Failed to insert new todo into”

+ TodoDatabaseProvider.CONTENT_URI);

finish();

return;

}

setResult(RESULT_OK, (new Intent()).setAction(uri.toString()));

} else {

Log.e(TAG, “Unrecognised action “ + action + “, exiting”);

finish();

return;

}

cursor = managedQuery(uri, TODO_PROJECTION, null, null, null);

setContentView(R.layout.todo_editor);

text = (EditText) findViewById(R.id.todo);

date = (EditText) findViewById(R.id.duedate);

if (savedInstanceState != null) {

initialTodo = savedInstanceState.getString(INITIAL_TODO);

}

}

Здесь про­из­во­дит­ся вы­зов про­вай­де­ра ба­зы дан­ных и пред­принима­ют­ся дей­ст­вия в со­от­вет­ст­вии с его ре­зуль­та­том. Об­ратите внимание, что в на­ча­ле мы встав­ля­ем стро­ку, а за­тем об­нов­ля­ем ее, когда ре­ша­ем со­хранить эле­мент спи­ска.

Вы­зо­ви­те этот код из Todo.java, до­ба­вив пункт ме­ню New Item [Но­вый эле­мент]. Мы так­же сно­ва до­бав­ля­ем пунк­ты ме­ню с по­мо­щью MenuInflater (см. код на DVD) для реа­ли­за­ции со­хранения, уда­ления и от­ме­ны (Save, Delete и Cancel). При со­хранении вы­зы­ва­ет­ся ме­тод updateTodo():

private final void updateTodo(String task, String duedate) {

ContentValues values = new ContentValues();

if (state == STATE_INSERT) {

values.put(TodoDatabaseProvider.COL_CREATEDATE, System.currentTimeMillis());

values.put(TodoDatabaseProvider.COL_TASK, task);

values.put(TodoDatabaseProvider.COL_DUEDATE, duedate);

}

getContentResolver().update(uri, values, null, null);

}

ContentValues по­лу­ча­ет зна­чения, ко­то­рые мы хо­тим до­ба­вить, а за­тем с по­мо­щью getContentResolver() мы по­лу­ча­ем про­вай­дера ба­зы дан­ных и вы­зы­ва­ем его ме­тод update(). По­сле это­го ак­тив­ным за­ня­ти­ем сно­ва станет ToDo, и мы уви­дим в спи­ске но­вый эле­мент.

У нас поч­ти за­кон­чи­лось ме­сто для статьи. По­ка мы ра­бо­та­ли толь­ко с таб­ли­цей за­дач без ссыл­ки на ка­те­го­рии; что­бы ото­бра­зить ка­те­го­рию для ка­ж­дой за­да­чи, нам нуж­но из­менить ме­тод populateList(), что­бы он по­лу­чал эту ин­фор­ма­цию.

Для это­го сна­ча­ла нуж­но на­пи­сать за­прос join, ко­то­рый ссы­ла­ет­ся на две таб­ли­цы. managedQuery() не под­дер­жи­ва­ет ра­бо­ту с несколь­ки­ми таб­ли­ца­ми, и нам при­дет­ся на­пи­сать спе­ци­аль­ный SQL-за­прос. Все это, а так­же код, ко­то­рый по­на­до­бит­ся вам для уда­ления и из­менения за­пи­сей в ба­зе дан­ных, мож­но най­ти на LXFDVD (или на www.linuxformat.com).

[править] До­машнее за­дание

Это при­ло­жение ра­бо­та­ет пре­крас­но, но оно до­воль­но про­стое. Вот несколь­ко ве­щей, ко­то­рые вы мо­же­те за­хо­теть по­про­бо­вать, что­бы луч­ше осво­ить ра­бо­ту API:

  • Ка­те­го­рия не мо­жет быть пустой, ее нуж­но за­да­вать.
  • Поль­зо­ва­тель не мо­жет ре­дак­ти­ро­вать доступ­ные ка­те­го­рии. Для их об­ра­бот­ки и для кор­рект­ной ра­бо­ты с На­ме­рения­ми для вы­зо­ва кор­рект­ных За­ня­тий вы ско­рее все­го соз­да­ди­те но­вое За­ня­тие (ре­дак­ти­ро­вать спи­сок/ре­дак­ти­ро­вать ка­те­го­рию).
  • По­сле за­вер­шения за­да­чи ее мож­но толь­ко уда­лить. Как на­счет про­став­ления га­лоч­ки?
  • Что­бы да­ты со­хра­ня­лись кор­рект­но, их нуж­но вво­дить в спе­ци­аль­ном фор­ма­те. Вме­сто это­го хо­ро­шо бы по­до­шел ка­лен­дарь или дру­гая фор­ма вво­да.

Взгляните на вид­жет да­ты в Android.

В сле­дую­щей ста­тье этой се­рии мы по­смот­рим, как по­лу­чать ин­фор­ма­цию из уда­лен­ной ба­зы дан­ных и за­пи­сы­вать в нее дан­ные. |

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