Статьи

Создание приложения для контактов с помощью jQuery Mobile и Android SDK — часть 4

В третьей части мы продолжили эту серию, объяснив, как добавить новый контакт. Мы также обсудили, как использовать 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) {
  }
}

Изучив код, давайте теперь посмотрим на конфигурацию и другие вспомогательные файлы для проекта.

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 этого руководства.
1
2
3
4
<?xml version=»1.0″ encoding=»utf-8″?>
<resources>
  <string name=»app_name»>Contacts</string>
</resources>

В strings.xml хранятся постоянные строки, используемые в приложении. Единственная константа, которую мы используем, это элемент app_name который является именем приложения. Значение этой константы, «Контакты», отображается в различных местах устройства Android, как показано на рисунке ниже: экран запуска приложений (слева), домашний экран (в центре) и экран управления приложениями (справа).

Имя приложения

Рисунок 17. Имя приложения.


Значки запуска приложения основаны на элементах графического интерфейса 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. Иконки запуска.


Теперь мы обсудим, как импортировать нативное приложение в среду разработки Eclipse. Файлы проекта были проверены на:

Проект был успешно протестирован на платформе Android 2.2 API уровня 8.

Перед импортом проекта в среду Eclipse убедитесь, что плагин Eclipse ADT указывает на правильное расположение Android SDK в вашей локальной системе. Чтобы проверить это, в меню Eclipse зайдите в Window -> Preferences -> Android . В окне SDK Location должно быть указано расположение Android SDK. После правильной настройки вы должны увидеть что-то похожее на

предпочтения

Рисунок 19. Настройки Eclipse.

Файлы проекта представлены в архивном файле с именем contacts.zip . Чтобы импортировать проект, в меню Eclipse выберите « File -> Import а затем в мастере импорта файлов выберите « General -> Existing Projects into Workspace (см. Ниже).

импорт

Рисунок 20. Импорт проекта.

На следующей странице мастера выберите Select archive file: и перейдите к месту расположения файла contacts.zip в вашей файловой системе. Окно « Projects будет автоматически заполнено, если проект ContactsDemo уже выбран. Это показано ниже. Нажмите кнопку Finish , чтобы завершить импорт.

Выбор файла проекта

Рисунок 21. Выбор файла проекта.

Eclipse создаст приложение автоматически после импорта. Теперь вы должны увидеть проект ContactsDemo в проводнике проекта, как показано ниже.

Project Explorer

Рисунок 22. Project Explorer.

Этот проект был построен и протестирован для платформы Android OS 2.2. Чтобы убедиться в этом, выберите проект ContactsDemo в проводнике проекта и в контекстном меню выберите « Properties . В левой части списка свойств выберите Android в качестве свойства. Доступные цели сборки отображаются справа, как показано ниже. Вы должны увидеть, что выбран Android 2.2.

Android Build Target

Рисунок 23. Цель сборки Android.

Список файлов в проекте приведен ниже.

Список файлов

Рисунок 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.