В третьей части мы продолжили эту серию, объяснив, как добавить новый контакт. Мы также обсудили, как использовать API Java Java для доступа к контактам на устройстве Android и управления ими. Это учебное пособие является заключительным в серии, и в нем мы расскажем, как удалить и сохранить контакт с помощью Android Java API. Мы также опишем среду разработки для приложения, обсудим файлы конфигурации для проекта и дадим отдельные шаги для импорта проекта в Eclipse IDE.
Операции записи для контактов
Теперь рассмотрим операции записи в отношении контакта. Это операции удаления и сохранения.
Удаление контакта
Следующий метод в классе ContactUtility
отвечает за удаление контакта.
1
2
3
4
5
6
7
8
9
|
public static void deleteContact(String id, ContentResolver contentResolver, String accountType){
HashMap<String,String> contacts = getUsersFromAccount(accountType, contentResolver);
String existingContactId = contacts.get(id);
if(existingContactId == null){
// The contact does not belong to account
return;
}
deleteContactInternal(id, contentResolver);
}
|
Как упоминалось ранее, мы не разрешаем удалять или изменять контакт в этом учебном приложении, если он не был создан самим приложением. (Это просто для того, чтобы избежать случайного повреждения контакта в реальном устройстве, учитывая тот факт, что это просто учебное приложение.) Чтобы определить, был ли контакт создан этим приложением, достаточно проверить, принадлежит ли контакт учетная запись с конкретным типом учетной записи для этого приложения. deleteContact()
метод deleteContact()
сначала выполняет метод с именем getUsersFromAccount()
который возвращает список всех идентификаторов контактов для данного типа учетной записи. Если идентификатор контакта, запрошенный для удаления, находится в этом списке, то deleteContactInternal()
метод deleteContactInternal()
для фактического удаления контакта. В противном случае метод deleteContact()
возвращается без удаления контакта.
Метод ContactUtility.getUsersFromAccount()
приведен ниже. Он использует таблицу, где имена предложений и столбцов в запросе «Контакты, связанные с учетной записью» выше.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
import java.util.HashMap;
…
private static HashMap<String,String> getUsersFromAccount(String accountType, ContentResolver contentResolver){
Cursor cursor = contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, null,
ContactsContract.RawContacts.ACCOUNT_TYPE + » = ? «, new String[] { accountType }, null);
HashMap<String,String> map = new HashMap<String,String>();
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID));
map.put(contactId, contactId);
}
}
return map;
}
|
Метод ContactUtility.deleteContactInternal()
указан ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
import android.net.Uri;
…
private static void deleteContactInternal(String id, ContentResolver contentResolver){
Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null,
ContactsContract.Contacts._ID + » = ? «, new String[]{id}, null);
String lookup = null;
if (cursor.getCount() > 0) {
while (cursor.moveToNext()) {
<B>lookup = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
}
}
cursor.close();
Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookup);
contentResolver.delete(uri, null, null);
}
|
Удаление контакта из базы данных состоит из этих шагов.
- Сначала запросите базу данных для получения записи контакта, используя
ContactsContract.Contacts.CONTENT_URI
в качестве представления таблицы на основе URI. - Используя
ContactsContract.Contacts.LOOKUP_KEY
в качестве дескриптора столбца, получите «ключ поиска» для контакта. Это уникальный идентификатор, который будет использоваться для удаления контакта. -
android.net.Uri
объектandroid.net.Uri
который создает представление уникального идентификатора контакта на основе URI. - Вызовите
ContentResolver.delete()
с представлениемUri
контакта, чтобы удалить его.
Сохранение контакта
Сохранение контакта происходит в двух сценариях. Контакт может быть существующим в базе данных, или это может быть новый контакт, для которого необходимо вставить связанные записи с нуля.
Для сохранения существующего контакта можно использовать разные стратегии. Например, существующие записи могут быть обновлены на основе идентификатора строки этих записей. В этом учебном приложении для простоты мы решили сохранить существующий контакт, сначала удалив его, а затем вставив обратно как новый контакт. Это простой подход, поскольку он использует уже написанные методы для удаления существующего контакта и сохранения нового контакта. Дополнительный код с операциями «update» не требуется.
Метод ContactUtility.saveOrUpdateContact()
приведен ниже. Он используется как для новых, так и для существующих контактов.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public static void saveOrUpdateContact(Contact contact,ContentResolver contentResolver, String accountName, String accountType){
if(contact == null || accountName == null || accountType == null){
return;
}
String id = contact.getContactId();
if(!»».equals(replaceNull(id))){
// This is existing contact to update
HashMap<String,String> contacts = getUsersFromAccount(accountType, contentResolver);
String existingContactId = contacts.get(id);
if(existingContactId == null){
// This is associated with another account — cannot process
return;
}
deleteContactInternal(id, contentResolver);
}
saveContact(contact,contentResolver, accountName, accountType);
}
|
- Существуют различные проверки работоспособности, чтобы избежать нулевых или тривиальных объектов. Метод
replaceNull()
, перечисленный ниже, преобразует пустую строку в пустую и является частью этих проверок работоспособности. - Если идентификатор не является пустой строкой, он должен соответствовать существующему контакту в базе данных. В этом случае мы проверяем, принадлежит ли он к учетной записи, связанной с этим приложением. (Метод
getUsersFromAccount()
был рассмотрен выше.) Если это не так, контакт не следует изменять, и метод возвращается без каких-либо изменений в учетной записи. - Если контакт принадлежит учетной записи, связанной с этим приложением, он удаляется.
- Наконец,
saveContact()
вызывается для сохранения контакта.
1
2
3
4
5
6
7
8
|
public static String replaceNull(String in){
if(in == null){
return «»;
}
else{
return in;
}
}
|
Метод ContactUtility.saveContact()
приведен ниже. Он определяет список экземпляров android.content.ContentProviderOperation
для вставки отдельных записей, а затем вызывает ContentResolver.applyBatch()
для одновременного выполнения всех этих операций.
- Первая операция связывает вновь созданную контактную запись с именем учетной записи и типом учетной записи для этого приложения. Напомним, что имя учетной записи было указано пользователем при первом создании учетной записи, а тип учетной записи — константа
com.jquerymobile.demo.contact
. - Метод
ContentProviderOperation.newInsert()
возвращает экземпляр классаandroid.content.ContentProviderOperation.Builder
, который обычно используется для определения значений параметров для объектаContentProviderOperation
. (См. Следующие ссылки дляContentProviderOperation
иBuilder
.) ОперацияBuilder.withValue()
возвращает тот же экземплярBuilder
что позволяет нам рекурсивно передавать значения столбцов для вставленной записи. - Предложение
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
позволяет связывать каждую запись вставки с первой записью вставки, в которую вставлена корневая запись контакта. - После первой записи вставки определяются дополнительные записи вставки для имени и фамилии, примечания, адресов, организаций, электронных писем, мгновенных сообщений и телефонов контакта.
- Наконец,
ContentResolver.applyBatch()
вызывается для выполнения операций пакетной вставки в базу данных.
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
|
import android.content.ContentProviderOperation;
…
private static void saveContact(Contact contact,ContentResolver contentResolver, String accountName, String accountType){
ArrayList<ContentProviderOperation>operations = new ArrayList<ContentProviderOperation>();
<B>// New contact record with account information</B>
operations.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName)
.build());
<B>// First and last names</B>
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.getFirstName())
.withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.getLastName())
.build());
<B>// Note</B>
if(contact.getNote() != null){
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Note.NOTE, contact.getNote().getText())
.build());
}
<B>// Addresses</B>
Collection<Address> addresses = contact.getAddresses();
if(addresses != null){
for(Address address:addresses){
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.TYPE,address.getType())
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.STREET,address.getStreet())
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.CITY,address.getCity())
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.REGION,address.getState())
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.POBOX,address.getPoBox())
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,address.getZip())
.withValue(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY,address.getCountry())
.build());
}
}
<B>// Organizations</B>
Collection<Organization> organizations = contact.getOrganizations();
if(organizations != null){
for(Organization organization:organizations){
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Organization.TYPE, organization.getType())
.withValue(ContactsContract.CommonDataKinds.Organization.DATA, organization.getName())
.withValue(ContactsContract.CommonDataKinds.Organization.TITLE, organization.getTitle())
.build());
}
}
<B>// Emails</B>
Collection<Email> emails = contact.getEmails();
if(emails != null){
for(Email email:emails){
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.TYPE,email.getType())
.withValue(ContactsContract.CommonDataKinds.Email.DATA,email.getValue())
.build());
}
}
<B>// IMs</B>
Collection<Im> ims = contact.getIms();
if(ims != null){
for(Im im:ims){
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Im.PROTOCOL,im.getProtocol())
.withValue(ContactsContract.CommonDataKinds.Im.DATA,im.getValue())
.build());
}
}
<B>// Phones</B>
Collection<Phone> phones = contact.getPhones();
if(phones != null){
for(Phone phone:phones){
operations.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE,phone.getType())
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER,phone.getNo())
.build());
}
}
try {
<B>contentResolver.applyBatch(ContactsContract.AUTHORITY,operations);</B>
} catch (Exception e) {
}
}
|
Конфигурация проекта и вспомогательные файлы
Изучив код, давайте теперь посмотрим на конфигурацию и другие вспомогательные файлы для проекта.
AndroidManifest.xml
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<?xml version=»1.0″ encoding=»utf-8″?>
<manifest xmlns:android=»http://schemas.android.com/apk/res/android»
<B>package=»com.jquerymobile.demo.contact»</B>
android:versionCode=»1″
android:versionName=»1.0″>
<B><uses-permission android:name=»android.permission.READ_CONTACTS»</B>/>
<B><uses-permission android:name=»android.permission.WRITE_CONTACTS»</B>/>
<B><uses-permission android:name=»android.permission.GET_ACCOUNTS»</B> />
<B><uses-permission android:name=»android.permission.AUTHENTICATE_ACCOUNTS»</B> />
<application android:debuggable=»true» android:icon=»@drawable/icon»
android:label=»@string/app_name»>
<<B>service</B>
android:name=<B>».authentication.AuthenticationService»</B>
android:exported=»true»>
<intent-filter>
<action android:name=»android.accounts.AccountAuthenticator» />
</intent-filter>
<meta-data android:name=»android.accounts.AccountAuthenticator»
android:resource=»@xml/authenticator» />
</<B>service</B>>
<activity android:name=<B>».ContactsActivity»</B>
android:configChanges=»orientation|keyboardHidden»
android:label=»@string/app_name»>
<intent-filter>
<action android:name=»android.intent.action.MAIN» />
<category android:name=»android.intent.category.LAUNCHER» />
</intent-filter>
</activity>
</application>
</manifest>
|
- Имя пакета для нашего приложения —
com.jquerymobile.demo.contact
, которое указывается в элементеmanifest
верхнего уровня. Объявления.authentication.AuthenticationService
и.ContactsActivity
относятся к имени пакета. - Мы перечисляем типы разрешений, требуемых приложением, с помощью элементов
uses-permission
. - Мы обсудили элемент
service
в «Создание учетной записи», часть 2 этого руководства.
strings.xml
1
2
3
4
|
<?xml version=»1.0″ encoding=»utf-8″?>
<resources>
<string name=»app_name»>Contacts</string>
</resources>
|
В strings.xml
хранятся постоянные строки, используемые в приложении. Единственная константа, которую мы используем, это элемент app_name
который является именем приложения. Значение этой константы, «Контакты», отображается в различных местах устройства Android, как показано на рисунке ниже: экран запуска приложений (слева), домашний экран (в центре) и экран управления приложениями (справа).
Значок запуска приложения
Значки запуска приложения основаны на элементах графического интерфейса Android в http://www.matcheck.cz/androidguipsd/ . В соответствии с Руководством по дизайну иконок Android были созданы три файла иконок, как описано ниже.
Имя папки | Имя файла | Размер пикселя |
res\drawable-ldpi |
icon.png |
36 x 36 |
res\drawable-mdpi |
icon.png |
48 x 48 |
res\drawable-hdpi |
icon.png |
72 x 72 |
Эти значки показаны на рисунке ниже. Значок слева — 36×36 пикселей, середина — 48×48 пикселей, справа — 72×72 пикселей.
Рисунок 18. Иконки запуска.Родная среда разработки приложений для Android
Теперь мы обсудим, как импортировать нативное приложение в среду разработки Eclipse. Файлы проекта были проверены на:
- Android SDK, редакция 8.
- Eclipse IDE версия 3.5
- Инструменты разработки Android (ADT) , являющиеся плагином Eclipse, версия 8.0.1.
Проект был успешно протестирован на платформе Android 2.2 API уровня 8.
Импорт проекта
Перед импортом проекта в среду Eclipse убедитесь, что плагин Eclipse ADT указывает на правильное расположение Android SDK в вашей локальной системе. Чтобы проверить это, в меню Eclipse зайдите в Window -> Preferences -> Android
. В окне SDK Location
должно быть указано расположение Android SDK. После правильной настройки вы должны увидеть что-то похожее на
Файлы проекта представлены в архивном файле с именем contacts.zip
. Чтобы импортировать проект, в меню Eclipse выберите « File -> Import
а затем в мастере импорта файлов выберите « General -> Existing Projects into Workspace
(см. Ниже).
На следующей странице мастера выберите Select archive file:
и перейдите к месту расположения файла contacts.zip
в вашей файловой системе. Окно « Projects
будет автоматически заполнено, если проект ContactsDemo
уже выбран. Это показано ниже. Нажмите кнопку Finish
, чтобы завершить импорт.
Eclipse создаст приложение автоматически после импорта. Теперь вы должны увидеть проект ContactsDemo в проводнике проекта, как показано ниже.
Рисунок 22. Project Explorer. Этот проект был построен и протестирован для платформы Android OS 2.2. Чтобы убедиться в этом, выберите проект ContactsDemo
в проводнике проекта и в контекстном меню выберите « Properties
. В левой части списка свойств выберите Android
в качестве свойства. Доступные цели сборки отображаются справа, как показано ниже. Вы должны увидеть, что выбран Android 2.2.
Список файлов
Список файлов в проекте приведен ниже.
Рисунок 24. Список файлов.- В папке
src
хранится код Java. Есть два пакета:- Пакет
com.jquerymobile.demo.contact
содержитcom.jquerymobile.demo.contact
Address
,Contact
,ContactDisplay
,ContactGroup
,ContactsActivity
,ContactUtility
,Email
,Im
,Note
,Organization
иPhone
. - В пакете
com.jquerymobile.demo.contact.authentication
содержится классAuthenticationService
.
- Пакет
- Папка
gen
содержит различные файлы, автоматически сгенерированные Eclipse ADT. - В папке
assets
хранятся файлы HTML, файлы изображений, используемые в этих файлах HTML, и библиотеки jQuery Mobile / jQuery. Мы используем jQuery Mobile версии 1.0 Alpha 3, которая была последней версией на момент написания руководства. (Недавно был выпущен релиз Alpha 4 с различными исправлениями ошибок. См. Объявление .) - В папке
lib
хранятся библиотеки JSON Джексона. - В папке
res
хранятся различные ресурсы, необходимые для приложения. Это изображения значков и файлы конфигурацииstrings.xml
иauthenticator.xml
. -
default.properties
— это сгенерированный системой файл, который определяет версию API для приложения Android. - Файл
proguard.cfg
автоматически создается средой разработки и используется инструментом ProGuard. Подробности можно найти в документации ProGuard .
Выводы
В этом уроке мы реализовали приложение для Android, в котором пользовательский интерфейс создается с помощью HTML / JavaScript, а основная функциональность — с помощью Java. Преимущество такого подхода заключается в том, что веб-разработчики, уже знакомые с HTML и JavaScript, могут использовать свои знания для создания пользовательского интерфейса, не изучая специфичные для Android API, модель обработки событий пользовательского интерфейса и язык программирования Java. С другой стороны, разработчики с опытом работы с Java могут сосредоточиться на создании нативной функциональности с помощью Android Java API. Таким образом, трудозатраты могут быть разделены между двумя или более разработчиками на основе существующих наборов навыков.
Типичное соображение дизайна для приложения Android заключается в том, что визуальные аспекты и модель обработки событий пользовательского интерфейса должны быть согласованными на разных устройствах, где будет установлено приложение. Эти устройства могут иметь разные размеры экрана и запускать разнообразные веб-браузеры с разными уровнями поддержки HTML. В этом отношении jQuery Mobile выгоден тем, что предоставляет легкодоступные компоненты пользовательского интерфейса с поддерживающей моделью обработки событий. Он уже был протестирован на согласованность между различными устройствами и браузерами, что облегчает кросс-платформенную разработку.
Наконец, обратите внимание, что некоторые приложения не будут вписываться в вышеуказанную модель. Например:
- Некоторым приложениям требуются сложные компоненты пользовательского интерфейса, например, сложные анимации, которые невозможно создать с помощью HTML-страниц.
- Можно использовать веб-платформу приложений, такую как PhoneGap, для доступа к собственным функциям через упрощенный API-интерфейс JavaScript, если API-интерфейс достаточен для удовлетворения бизнес-требований. В этом случае пользовательский интерфейс все еще может быть построен с помощью jQuery Mobile, однако может не потребоваться разработка внутреннего кода Java.