Dieser Artikel zeigt die Nutzung des ContactsContracts zum Abrufen der Kontaktdaten auf einem Android Smartphone. Er ist eine Überarbeitung des Artikels von Kevin Kratzer “Working with the “ContactsContract to query contacts in Android”, der am 14.3.2011 hier in diesem Blog erschienen ist.
Kontakte abrufen
Normalerweise ist die Android Developer Seite ein guter Anlaufpunkt, um einen schnellen Einstieg in verschiedene Thematiken im Bereich der Android Entwicklung zu erhalten. Wenn man jedoch den Beitrag zum Abrufen von Kontakten auf der Webseite betrachtet, findet man ein veraltetes Beispiel: es benutzt Klassen und Felder der Android API, die schon seit langem als deprecated markiert sind. Da es Entwickler gibt, die sich an diesem Beispiel orientieren und daher noch mit dieser alten Funktionalität arbeiten, haben wir uns einmal den neueren Weg angeschaut, der nun in der Android API empfohlen wird. Dieser basiert auf den Funktionalitäten der ContactsContract Klasse und Subklassen.
Um die Features der neuen API zu demonstrieren, durchsucht die Beispiel-App alle verfügbaren Adressbücher auf dem Handy und zusätzlich häufig verwendete Informationen aus dem contacts content provider, wie z.B. den Namen, Telefonnummern, E-Mail-Adressen und natürlich das Foto.
Sie können sich den Code anschauen oder ihn downloaden in unserem SVN-Repository bei Google Code (http://code.google.com/p/android-contacts-contract-example/) oder verwenden Sie svn.
ContactsContract Funktionalität
Die ContactsContract Funktionalität wurde mit API-Level 5 (Android 2.0) eingeführt und später mit API-Level 11 (Android 3.0) verbessert.
Da nach der Erhebung vom 1. Dezember 2011 über die Verteilung der Geräte, die den Android Market genutzt haben, noch 95,5% zwischen API-Level 5 und 11 sind, haben wir bei der Erstellung der Beispiel-App keines der verbesserten Features benutzt. Dadurch kann der gezeigte Code direkt auf der Mehrheit der vorhandenen Android Geräte ausgeführt werden. Des Weiteren ist der gezeigte Code auch in einem Emulator und auf dem Galaxy Nexus mit Android 4.0 funktionsfähig. Dennoch weisen wir in dem Artikel auf die Änderungen hin, die in API-Level 11 eingeführt wurden.
Content Provider
Werfen wir zunächst einen kurzen Blick auf das Grundkonzept, wie man mit Hilfe eines Content Providers auf Daten zugreift.
Content Provider stellen ihre Daten auf eine ähnlich Weise bereit, wie dies eine SQL Datenbank macht. Jeder Content Provider und die darin enthaltenen Daten können durch eine eindeutige Adresse (URI – Unique Resource Identifier) identifiziert und abgefragt werden, wie z.B. “content://com.appsolut.example/exampleData”.
Das grobe Vorgehen zum Zugreifen auf Daten eines bestimmten Content Providers beinhaltet zunächst die Erstellung einer Query (Abfrage), welche dann nach der Ausführung einen Cursor als Ergebnis liefert. Dieser Cursor repräsentiert die angefragten Daten als ein Objekt mit Random Access.
Die Konfiguration von Querys ist unkompliziert und kann in fünf Schritten beschrieben werden:
- Identifizieren Sie den Unique Ressource Identifier (URI) des gewünschten Content Providers.
- Erzeugen Sie ein String-Array mit den Namen der Spalten (Projection-Klausel), die Sie aus der Datenbank benötigen (z.B. RawContacts.CONTACT_ID). Dies nennt man projection.
- Wenn Sie die Ergebnisse der Abfrage filtern möchten, definieren Sie einen String als Selection-Klausel (z.B. filtern mit Kontakt-Ids: RawContacts.CONTACT_ID + “=?”). Der ? Operator fungiert als Parameter, der im nächsten Schritt definiert wird. Dies nennt man selection.
- Erstellen Sie ein weiteres String Array für alle Parameter, die Sie in Ihrer Selection-Klausel verwendet haben. Für das obige Beispiel könnte es so etwas wie new String[]{ContactID} sein. Das Array bezeichnet man als selectionArgs. Wenn keine Parameter verwendet wurden, ignorieren Sie diesen Schritt einfach.
- Wenn Sie Ihre Ergebnisse nach Spalten sortieren möchten, definieren Sie eine Sortierreihenfolge wie RawContacts.CONTACT_ID + “ASC”, die die Ergebnisse in aufsteigender Reihenfolge nach den Kontakt-IDs sortiert. Dieser String wird als sortOrder bezeichnet.
Mit den Parametern aus diesen fünf Schritten kann die Abfrage-Methode innerhalb einer Activity, eines Services oder einer Application aufgerufen werden (siehe Listing 1).
Listing 1: Query Methode
public final Cursor managedQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
Mit dem Cursor können Zeilen ausgewählt und bestimmte Werte der Spalten einer Zeile zurückgegeben werden (mit Gettern wie getString oder getInt).
Content Provider Beschreibung
Eine detailliertere Beschreibung von Content Providern liefert die Android-Dokumentation unter http://developer.android.com/guide/topics/providers/Content Providers.html.
Folgen Sie aber nicht dem dort vorgeschlagenen Weg, auf den Content Provider zuzugreifen, da er veraltet ist.
Abfrage Methode für API Level-11+
Beachten Sie: ab API-Level 11 wird auch die managedQuery Methode als deprecated markiert.
Das neue Konzept ist aber sehr ähnlich. Es funktioniert nicht mehr über die managedQuery Methode, sondern über das LoaderManager.LoaderCallbacks<Cursor> Interface und die CursorLoader Klasse. Die durch das LoaderCallbacks Interface definierte Callback Methode
public Loader<Cursor> onCreateLoader(int id, Bundle args)
kann dann genutzt werden, um den CursorLoader mit Hilfe des Konstruktors
public CursorLoader (Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
analog zu der managedQuery Methode zu instanziieren.
Die Callback Methode
public void onLoadFinished(Loader<Cursor> loader, Cursor data)
liefert im Anschluss asynchron die Daten als Cursor.
Das Tutorial der Android Developers auf http://developer.android.com/guide/topics/fundamentals/loaders.html bietet weitere Informationen zu diesem neuen Konzept.
Abfrage von Kontakten
Nach den grundlegenden Aspekten beschäftigen wir uns jetzt mit Abfragen der Kontakte und mit Informationen zu den Kontakten. Für diese Zwecke wurden in API Level 5 die “android.provider.ContactsContract“ Klasse/Subklassen eingeführt.
Bevor Sie den ContactsContract benutzen können, müssen Sie im ersten Schritt der App die nötigen Rechte in der Projekt Manifest-Datei zuweisen. Dies kann entweder direkt in der XML-Datei vorgenommen (siehe Listing 2) oder über den Eclipse Manifest Editor eingestellt werden.
Listing 2: Rechte in der XML-Manifest Datei
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.appsolut.example.queryContacts" android:versionCode="1" android:versionName="1.0">
<uses-permission android:name="android.permission.READ_CONTACTS" />
…
</manifest>
Ohne die Rechte würde Ihre App mit der Exception „java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2“ abstürzen, wenn Sie auf den Content Provider zugreifen.
Eine Liste aller verfügbaren Kontakte abfragen
Jetzt können Sie damit beginnen, auf den “ContactsContract.RawContacts” (http://developer.android.com/reference/android/provider/ContactsContract.RawContacts.html) Content Provider zuzugreifen, um alle verfügbaren Kontakte abzufragen, die im Smartphone gespeichert sind.
Diese Tabelle stellt jede Person als einzelnen Eintrag (eine Zeile) dar. Jeder Person ist eine eindeutige ID zugeordnet, die im RawContacts.CONTACT_ID Feld (Spalte) gespeichert ist.
Im Gegensatz zur _ID Spalte wird CONTACT_ID auch in anderen Tabellen verwendet – so können Sie diese später nutzen, um zusätzliche Informationen über diese Person abzufragen.
Um Zugang zum RawContacts Content Provider zu erhalten, müssen Sie zunächst eine Projektion definieren (siehe Listing 3).
Listing 3: Projection-Klausel
CONTACT_ID und DELETED
final String[] projection = new String[] {
RawContacts.CONTACT_ID, // the contact id column
RawContacts.DELETED // column if this contact is deleted
};
Interessant für Sie sind hierbei die CONTACT_ID Spalte und die DELETED Spalte. Die DELETED Spalte ist wichtig, da es Einträge in der RawContacts Tabelle geben kann, die gelöscht wurden und daher nicht mehr angezeigt werden sollen.
Mit Hilfe der Projektion können Sie jetzt den Cursor erstellen, indem Sie eine Query benutzen (siehe Listing 4).
Listing 4: Query zur Abfrage aller vorhandenen Kontakte
final Cursor rawContacts = managedQuery(
RawContacts.CONTENT_URI, //the URI for raw contact provider
projection
null, //selection = null, retrieve all entries
null, //selection is without parameters
null); //do not order
Mit diesem Cursor können Sie alle verfügbaren Kontakt-IDs durchlaufen. Dazu müssen Sie den Index der Kontakt-ID und der DELETED Spalte identifizieren. Mit Hilfe der getColumnIndex Methode des Cursor-Objekts können Sie diesen Index erhalten (siehe Listing 5).
Listing 5: CONTACT_ID und DELETED Spalten identifizieren
final int contactIdColumnIndex = rawContacts.getColumnIndex(RawContacts.CONTACT_ID);
final int deletedColumnIndex = rawContacts.getColumnIndex(RawContacts.DELETED);
Um sicherzustellen, dass der Cursor an den Anfang zeigt und dass es überhaupt gültige Einträge gibt, bietet sich die moveToFirst Methode an. Diese Methode setzt den Cursor auf den ersten Eintrag und gibt true zurück, wenn Einträge vorhanden sind. Nun können Sie alle Einträge mit einer while-Schleife durchlaufen, welche die isAfterLast Methode des Cursors als Abbruchbedingung nutzt. Diese Methode gibt true zurück, wenn der Cursor auf einen nicht existierenden Eintrag verweist (siehe Listing 6).
Listing 6: Iteration über alle verfügbaren Kontakte
if(rawContacts.moveToFirst()) {
while(!rawContacts.isAfterLast()) {
final int contactId = rawContacts.getInt(contactIdColumnIndex);
final boolean deleted = (rawContacts.getInt(deletedColumnIndex) == 1);
if(!deleted) {
doSomethingWithAContactId(contactId));
}
rawContacts.moveToNext();// move to the next entry
}
}
Um die Ressourcen wieder freizugeben, ist es wichtig, dass Sie den Cursor schließen, nachdem die Daten nicht mehr benötigt werden. Dies geschieht mit der
rawContacts.close();
Methode.
Informationen zu einem Kontakt abfragen
Mit einer spezifischen Kontakt ID können Sie auf weitergehende Informationen über diese Person in der “ContactsContract.Contacts” Tabelle (http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html) zugreifen.
In unserer Beispiel App fragen wir den Namen des Kontaktes sowie die Foto-ID ab. Die Foto-ID ist ein Verweis auf den Foto-Eintrag in der Datentabelle und kann somit später verwendet werden, um das Foto als Bitmap anzufordern.
Um dies in Ihrer App zu realisieren, müssen Sie wieder eine Query erstellen. Als erstes definieren Sie eine Projektion für die DISPLAY_NAME und PHOTO_ID Spalten (siehe Listing 7).
Listing 7: Projektion DISPLAY_NAME und PHOTO_ID
final String[] projection = new String[] {
Contacts.DISPLAY_NAME,// the name of the contact
Contacts.PHOTO_ID // the id of the column in the data table for the image
};
Mit Hilfe des selection und selectionArgs Parameters der Query Methode können Sie die Ergebnisse der Abfrage zusätzlich nach der spezifischen Kontakt-ID filtern (siehe Listing 8).
Listing 8: Query zur Abfrage von Name und Foto ID
final Cursor contact = managedQuery(Contacts.CONTENT_URI,projection,Contacts._ID + "=?", // filter entries on the basis of the contact id
new String[]{String.valueOf(contactId)},// the parameter to which the contact id column is compared to
null);
Nun können Sie die benötigten Informationen nutzen (siehe Listing 9).
Listing 9: Auswertung des Kontaktnamens und dessen Foto-ID
if(contact.moveToFirst()) {
final String name = contact.getString(
contact.getColumnIndex(Contacts.DISPLAY_NAME));
final String photoId = contact.getString(
contact.getColumnIndex(Contacts.PHOTO_ID));
doSomethingWithAContactName(name);
doSomethingWithAContactPhotoId(photoId);
}
contact.close();
Kontakt Fotos abfragen
Die Kontakt Fotos werden als Binary Large Objects (BLOB) in der “ContactsContract.Data” Tabelle gespeichert. In dieser Tabelle sind alle Arten von Daten zu einem Kontakt abgelegt, so dass eine bestimmte Foto ID benötigt wird, um den richtigen Eintrag abzurufen (wie in „Informationen zu einem Kontakt abfragen“ beschrieben). Die Spalte, in der das BLOB gespeichert ist, ist in der “CommonDataKinds.Photo” Klasse definiert. Mit dieser Photo.PHOTO Spalte können Sie ihre Query erstellen (siehe Listing 10).
Listing 10: Query zur Abfrage des Kontakt Fotos
final Cursor photo = managedQuery(
Data.CONTENT_URI,new String[] {Photo.PHOTO},// column for the blobData.
_ID + "=?",// select row by id
new String[]{photoId},// filter by a given photoId (as retrieved in listing 9)
null);
Wenn ein Foto mit dem Eintrag des Kontaktes verlinkt ist, gibt der Cursor das Foto-BLOB zurück. Mit der “BitmapFactory” Klasse können Sie daraus ein Bitmap erstellen (siehe Listing 11).
Listing 11: Bitmap aus BLOB erstellen
if(photo.moveToFirst()) {
byte[] photoBlob = photo.getBlob(
photo.getColumnIndex(Photo.PHOTO));
final Bitmap photoBitmap = BitmapFactory.decodeByteArray(
photoBlob, 0, photoBlob.length);
doSomethingWithAContactPhoto(photoBitmap);
}
photo.close();
Telefonnummern eines Kontaktes abfragen
Die Telefonnummern werden in der “ContactsContract.Data” Tabelle gespeichert. Jede Nummer wird durch einen Eintrag in dieser Tabelle dargestellt. Für den Zugriff auf die Spalten für Telefonnummern verwenden wir die Definitionen der “ContactsContract.CommonDataKinds.Phone” Klasse.
Es stehen drei verschiedene Spalten zur Verfügung: Nummer, Typ und Label. Die Typ Spalte wird verwendet, um die Art der Nummer zu definieren, z.B. Work, Home oder other. Wenn der Typ „other“ definiert ist, kann die Label Spalte dazu verwendet werden, den definierten Namen dieses Typs zu erhalten. Um das Beispiel übersichtlich zu halten, verzichten wir auf Systemunbekannte Label; daher enthält unsere Projection-Klausel keine Label Spalte (siehe Listing 12).
Listing 12: Projektion Nummer und Typ
final String[] projection = new
String[] {
Phone.NUMBER,
Phone.TYPE,
};
Als URI für die Telefonnummereinträge können Sie die “Phone.CONTENT_URI” URI nehmen, die die Datentabelle nach dem Medien-Typ der Telefonnummern filtert. Da wir nur Telefonnummern von einem bestimmten Kontakt abfragen möchten, filtern wir die Ergebnisse auf der Grundlage einer vorgegebenen CONTACT_ID (siehe Listing 13).
Listing 13: Cursor zur Abfrage der Telefonnummern
final Cursor phone = managedQuery(
Phone.CONTENT_URI,
projection,
Data.CONTACT_ID + "=?",
new
String[]{String.valueOf(contactId)},
null);
Da ein Kontakt mehrere Telefonnummereinträge besitzen kann, sollten Sie wieder eine while-Schleife zur Iteration über diesen Cursor nutzen. Um eine lesbare Version des Telefonnummer-Typs zu erhalten, verwenden Sie die getTypeLabelResource Methode der Phone Klasse, um die Android Resource ID des Labels für einen bestimmten Typ zu erhalten (siehe Listing 14).
Listing 14: Iteration über alle Telefonnummern eines Kontaktes
if(phone.moveToFirst()) {
final int contactNumberColumnIndex = phone.getColumnIndex(Phone.NUMBER);
final int contactTypeColumnIndex = phone.getColumnIndex(Phone.TYPE);
while(!phone.isAfterLast()) {
final String number = phone.getString(contactNumberColumnIndex);
final int type = phone.getInt(contactTypeColumnIndex);
final int typeLabelResource = Phone.getTypeLabelResource(type);
doSomethingWithAContactPhoneNumber(number, typeLabelResource);
phone.moveToNext();
}
}phone.close();
E-Mail Adresse eines Kontaktes abfragen
Auch die E-Mail-Adressen eines Kontaktes sind in der “ContactsContract.Data” Tabelle gespeichert. Um die gewünschten Informationen abzurufen, können Sie die definierten Spaltennamen und die URI der “CommonDataKinds.Email” Klasse verwenden (siehe Listing 15).
E-Mail Projection-Klausel für API Level 11+
Mit API Level 11 wurde ein zusätzliches Feld für die E-Mail Adresse zu der ”CommonDataKinds.Email” Klasse hinzugefügt. Das Email.ADDRESS Feld identifiziert die Adresse direkt, wodurch man nicht mehr Email.DATA nutzen muss und sollte (siehe Listing 15).
Listing 15: Projection-Klausel für E-Mail Adressen
final String[] projection = new String[] {
Email.DATA,// use Email.ADDRESS for API-Level 11+
Email.TYPE
};
Der Typ der E-Mail-Adresse ist genauso implementiert wie der Typ der Telefonnummern. Mit der getTypeLabelResource Methode der Email Klasse können Sie eine lesbare Android Ressource ID für die Beschreibung des Typs abrufen. Damit können Sie nun einen Cursor kreieren, um alle verfügbaren E-Mail-Adressen anzufordern (siehe Listing 16).
Listing 16: Query für E-Mail Adressen
final Cursor email =
managedQuery(Email.CONTENT_URI,
projection, Data.CONTACT_ID + "=?",
new
String[]{String.valueOf(contactId)},
null);
Genau wie bei der Abfrage der Telefonnummern können sie nun über den E-Mail-Cursor iterieren, um die Adressen zu extrahieren (siehe Listing 17).
Listing 17: Extraktion der E-Mail Adressen
if(email.moveToFirst()) {
final int contactEmailColumnIndex =
email.getColumnIndex(Email.DATA);
final int contactTypeColumnIndex =
email.getColumnIndex(Email.TYPE);
while(!email.isAfterLast()) {
final String address =
email.getString(contactEmailColumnIndex);
final int type = email.getInt(contactTypeColumnIndex);
final int typeLabelResource =
Email.getTypeLabelResource(type);
doSomethingWithAContactEmailAddress(address, typeLabelResource);
email.moveToNext();
}
}email.close();
Erweiterte Abfragen
Es ist auch möglich, SQLLite Operatoren in der Selection-Klausel zu nutzen. Unter www.sqlite.org/lang_expr.html können Sie eine Liste der möglichen Operatoren finden.
Ein Beispiel für den Einsatz eines Operators wäre der like Operator, welcher die Platzhalter „_“ für ein Zeichen und „%“ für mehrere Zeichen unterstützt. Mit Hilfe dieses Operators kann beispielsweise eine Suche nach einem Kontakt implementiert werden (siehe Listing 18).
Listing 18: Beispiel für eine erweiterte Abfrage - Suche nach Kontakten
searchContacts(String searchName) {
final String[] projection = new
String[] {
Contacts.DISPLAY_NAME,
};
final String selection =
Contacts.DISPLAY_NAME + " like ?";
final String[] selectionArgs = new
String[] {
"%" + searchName + "%"
};
final Cursor contact = managedQuery(Contacts.CONTENT_URI, projection, selection, selectionArgs, null);
if(contact.moveToFirst()) {
while(!contact.isAfterLast()) {
final String name = contact.getString(contact.getColumnIndex
Contacts.DISPLAY_NAME));
// do something here
contact.moveToNext();
}
}contact.close();
}
Zusammenfassung
Die vorgestellten Methoden sind in der Beispiel App, die auf der CD enthalten ist, implementiert. Diese App frägt die Details ab und stellt die Informationen in einer GUI dar. Mit dieser App können Sie ganz einfach die Funktionen testen. Weitere Informationen wie die Adresse, Instant Messenger, Notizen, etc. können ohne Probleme analog zu der vorgestellten Art abgefragt werden. Für zusätzliche Datenfelder schauen Sie in die Klassen des “android.provider.ContactsContract.CommonDataKinds”-Pakets.
Bitte beachten Sie, dass die Beispiel App nicht auf Performance optimiert wurde. Es wurde geschrieben, um auf eine klare und leicht verständliche Art zu zeigen, wie man auf Informationen über Kontakte zugreifen kann.
Tags: Android, Java, Software Development