14

我正在写一个Android应用程序,它具有代表一个人(特别是,一个孩子的父母或监护人)的数据类型。我希望能够从Android设备的“联系人”数据库​​中“导入”相关数据字段。 (这应该是可选的;也就是说,父母/监护人已不在联系人数据库中,如果他们添加了新的父母/监护人,也不会更新联系人数据库。)从Intent.Action返回的URI获取特定的联系信息

到目前为止,我已经编写了代码来启动一个新的Intent来选择特定的联系人(使用Intent.ACTION_PICK)。然后我得到一个代表数据库中特定联系人的URI。

不幸的是,我不知道下一步是什么。看起来这应该是世界上最简单的事情,但显然不是。我已阅读了Android开发人员网站上的文档,并浏览了不止一本Android书籍。没有快乐。

我想获得的具体信息,就是:

  1. 联系人姓名(第一分开,如果可能持续)

  2. 接触的(主要)的电子邮件地址

  3. 这个联系人的手机号码

I想象一下,通过使用ContentResolver查询,这应该是可能的,但我不知道如何使用从Intent返回的URI执行此操作。大多数文档假定您有联系人ID,而不是联系人的URI。此外,我不知道我可以将什么样的字段放入查询的投影中,假设这甚至是正确的方式来执行我想要的操作。

这里是我的出发代码:

// In a button's onClick event handler: 
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI); 
startActivityForResult(intent, PICK_CONTACT); 

// In onActivityResult: 
if (resultCode == RESULT_OK) { 
    if (requestCode == PICK_CONTACT) { 
     contactURI = data.getData(); 
     // NOW WHAT? 
    } 
} 

回答

14

事实证明,有更好的方法来做到这一点。

正如我所提到的,ContactsContract.Contacts.Entity类在API 11之前是不可用的。但是,ContactsContract.Data类在API 5中可用,您可以使用该类的方式与您使用实体类。

我已更新我的代码。它与Entity类的代码非常相似,其工作原理基本相同。不过,我已经使用运行姜饼的手机对它进行了测试,并且它工作得很好。

一个变化,我不得不做出的是:似乎没有成为一个方式来获得初始查询的ContactsContract.Data.RAW_CONTACT_ID,该ID是一样的,你从例如获得ID ContactsContract.Contacts._ID。相反,我查询了ContactsContract.Contacts.DISPLAY_NAME常量,这在几乎每个ContactsContract类中都是一致的。

这里的工作代码:

 Cursor cursor; // Cursor object 
     String mime; // MIME type 
     int dataIdx; // Index of DATA1 column 
     int mimeIdx; // Index of MIMETYPE column 
     int nameIdx; // Index of DISPLAY_NAME column 

     // Get the name 
     cursor = getContentResolver().query(params[0], 
       new String[] { ContactsContract.Contacts.DISPLAY_NAME }, 
       null, null, null); 
     if (cursor.moveToFirst()) { 
      nameIdx = cursor.getColumnIndex(
        ContactsContract.Contacts.DISPLAY_NAME); 
      name = cursor.getString(nameIdx); 

      // Set up the projection 
      String[] projection = { 
        ContactsContract.Data.DISPLAY_NAME, 
        ContactsContract.Contacts.Data.DATA1, 
        ContactsContract.Contacts.Data.MIMETYPE }; 

      // Query ContactsContract.Data 
      cursor = getContentResolver().query(
        ContactsContract.Data.CONTENT_URI, projection, 
        ContactsContract.Data.DISPLAY_NAME + " = ?", 
        new String[] { name }, 
        null); 

      if (cursor.moveToFirst()) { 
       // Get the indexes of the MIME type and data 
       mimeIdx = cursor.getColumnIndex(
         ContactsContract.Contacts.Data.MIMETYPE); 
       dataIdx = cursor.getColumnIndex(
         ContactsContract.Contacts.Data.DATA1); 

       // Match the data to the MIME type, store in variables 
       do { 
        mime = cursor.getString(mimeIdx); 
        if (ContactsContract.CommonDataKinds.Email 
          .CONTENT_ITEM_TYPE.equalsIgnoreCase(mime)) { 
         email = cursor.getString(dataIdx); 
        } 
        if (ContactsContract.CommonDataKinds.Phone 
          .CONTENT_ITEM_TYPE.equalsIgnoreCase(mime)) { 
         phone = cursor.getString(dataIdx); 
         phone = PhoneNumberUtils.formatNumber(phone); 
        } 
       } while (cursor.moveToNext()); 
      } 
     } 
+0

我得到一个警告params [0]这是什么? –

+0

非常感谢您的回答! – Chisko

+0

@GerardGrundy Karl在一个AsyncTask中实现了这个解决方案,由于查询内容解析器的操作可能很长,所以推荐使用这种解决方案。params []数组(可以被命名为你想要的,但是“params”和“args”是通用名称)是数据如何传递到AsyncTask中的。查看[AsyncTask](https://developer.android.com/reference/android/os/AsyncTask.html)文档以获取更多信息。 – Slerig

16

好吧,大量挖掘后,我发现我所相信的是答案。我发现的解决方案根据您使用的Android API级别而有所不同。然而,他们并不漂亮,所以如果有更好的解决方案,我很想知道。

在任何情况下,第一步是通过对从Intent.ACTION_PICK返回的URI执行查询来获取联系人的ID。当我们在这里时,我们也应该得到显示名称,以及表示联系人是否有电话号码的字符串。 (我们将不再需要他们为现代化的解决方案,但我们需要他们为传统的解决方案。)

String id, name, phone, hasPhone; 
int idx; 
Cursor cursor = getContentResolver().query(contactUri, null, null, null, null); 
if (cursor.moveToFirst()) { 
    idx = cursor.getColumnIndex(ContactsContract.Contacts._ID); 
    id = cursor.getString(idx); 

    idx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); 
    name = cursor.getString(idx); 

    idx = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER); 
    hasPhone = cursor.getString(idx); 
} 

根据记录,从这个URI返回的列大部分领域的常量表示在ContactsContract.Profile类中(包括从其他接口继承的常量)。不包括PHOTO_FILE_ID,PHOTO_THUMBNAIL_URI或PHOTO_URI(但PHOTO_ID 包含)。

现在我们有了ID,我们需要获取相关数据。第一个(也是最简单的)解决方案是查询一个实体。实体查询一次检索联系人或原始联系人的所有联系人数据。每行代表一个单一的Raw Contact,使用ContactsContract.Contacts.Entity中的常量访问。通常你只关心RAW_CONTACT_ID,DATA1和MIMETYPE。但是,如果分别需要姓氏和名字,则名称MIME类型包含DATA2中的名字和DATA3中的姓氏。

通过将MIMETYPE列与ContactsContract.CommonDataKinds常数进行匹配来加载变量;例如,电子邮件MIME类型在ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE

// Build the Entity URI. 
Uri.Builder b = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id).buildUpon(); 
b.appendPath(ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); 
URI contactUri = b.build(); 

// Create the projection (SQL fields) and sort order. 
String[] projection = { 
     ContactsContract.Contacts.Entity.RAW_CONTACT_ID, 
     ContactsContract.Contacts.Entity.DATA1, 
     ContactsContract.Contacts.Entity.MIMETYPE }; 
String sortOrder = ContactsContract.Contacts.Entity.RAW_CONTACT_ID + " ASC"; 
cursor = getContentResolver().query(contactUri, projection, null, null, sortOrder); 

String mime; 
int mimeIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.MIMETYPE); 
int dataIdx = cursor.getColumnIndex(ContactsContract.Contacts.Entity.DATA1); 
if (cursor.moveToFirst()) { 
    do { 
     mime = cursor.getString(mimeIdx); 
     if (mime.equalsIgnoreCase(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) { 
      email = cursor.getString(dataIdx); 
     } 
     if (mime.equalsIgnoreCase(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) { 
      phone = cursor.getString(dataIdx); 
     } 
     // ...etc. 
    } while (cursor.moveToNext()); 
} 

不幸的是,实体没有引入unti API 11(Android 3.0的,蜂窝),这意味着这个代码是与在市场上Android设备(在本文写作)的大致65%的不兼容。如果您尝试它,您将从URI获得IllegalArgumentException

第二种解决方案是建立一个查询字符串,使一个查询每个数据类型要使用:

// Get phone number - if they have one 
if ("1".equalsIgnoreCase(hasPhone)) { 
    cursor = getContentResolver().query(
      ContactsContract.CommonDataKinds.Phone.CONTENT_URI, 
      null, 
      ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = "+ id, 
      null, null); 
    if (cursor.moveToFirst()) { 
     colIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); 
     phone = cursor.getString(colIdx); 
    } 
    cursor.close(); 
} 

// Get email address 
cursor = getContentResolver().query(
     ContactsContract.CommonDataKinds.Email.CONTENT_URI, 
     null, 
     ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + id, 
     null, null); 
if (cursor.moveToFirst()) { 
    colIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS); 
    email = cursor.getString(colIdx); 
} 
cursor.close(); 

// ...etc. 

很明显,将导致大量的单独的数据库查询这种方式,所以它的不建议出于效率原因。

我已经想出了解决的办法是尽量使用实体查询的版本,赶抛出:IllegalArgumentException,并把遗留代码catch块:

try { 
    // Build the Entity URI. 
    Uri.Builder b = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id).buildUpon(); 
    b.appendPath(ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); 
     // ...etc... 
} catch (IllegalArgumentException e) { 
    // Get phone number - if they have one 
    if ("1".equalsIgnoreCase(hasPhone)) { 
     // ...etc... 
} finally { 
    // If you want to display the info in GUI objects, put the code here 
} 

我希望这可以帮助别人。而且,如果有更好的方法来做到这一点,我就会全神贯注。

+0

谢谢你,根据这个答案我动摇了'实体'表的权力 – DoruChidean

1
//Add a permission to read contacts data to your application manifest. 
<uses-permission android:name="android.permission.READ_CONTACTS"/> 

//Use Intent.ACTION_PICK in your Activity 
Intent contactPickerIntent = new Intent(Intent.ACTION_PICK, 
       ContactsContract.CommonDataKinds.Phone.CONTENT_URI); 
     startActivityForResult(contactPickerIntent, RESULT_PICK_CONTACT); 

//Then Override the onActivityResult() and retrieve the ID,Phone number and Name in the data. 
@Override 
    protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
     // check whether the result is ok 
     if (resultCode == RESULT_OK) { 
      // Check for the request code, we might be usign multiple startActivityForReslut 
      switch (requestCode) { 
      case RESULT_PICK_CONTACT: 
       Cursor cursor = null; 
     try { 
      String phoneNo = null ; 
      String name = null;   
      Uri uri = data.getData();    
      cursor = getContentResolver().query(uri, null, null, null, null); 
      cursor.moveToFirst();   
      int phoneIndex =cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); 
      phoneNo = cursor.getString(phoneIndex); 
      textView2.setText(phoneNo); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
       break; 
      } 
     } else { 
      Log.e("MainActivity", "Failed to pick contact"); 
     } 
    } 

注:Android 2.3的(API级别9)之前,执行对联系人提供的查询(如上面所示)要求您的应用程序申报READ_CONTACTS权限(请参阅安全性和权限)。但是,从Android 2.3开始,Contacts/People应用程序授予您的应用程序临时权限,以便在返回结果时从联系人提供程序读取。临时权限仅适用于请求的特定联系人,因此除非声明READ_CONTACTS权限,否则您无法查询除意图的Uri指定的联系人以外的联系人。