Android摸索记录--获取android联系人
Android摸索记录--获取android联系人
既然要做提醒、通知类的app,首先想尝试的就是支持自己手机通讯录的生日提醒。
这里说的android联系人,指的是存储在安卓手机google账户下面的联系人,而不是存储在本机或者某个国产手机系统的联系人。
android联系人
概况
联系人的数据大致分为16类,分别是
- 电子邮件(地址、电子邮件类型(家庭、工作、手机、其他))。
- 即时消息(协议(qq、icq、skype 等)、im id)。
- 昵称。
- 组织(公司、部门、职务、职位描述、办公地点)。
- 电话(号码、电话类型(家庭、手机、工作、其他))。
- sip地址
- 姓名(显示名称、名字、姓氏)。
- 邮政地址(国家、城市、地区、街道、邮政编码)。
- 身份(命名空间(SSN、护照)、号码)。
- 照片。
- 组(联系人属于组 id)。
- 网站(网站 url,网站类型())。
- 笔记。
- 事件(生日、其它自定义事件)
- 关系
- msic
对于某些组,例如电子邮件、电话、地址等。信息还根据数据使用情况进行分类,例如在家中使用、在工作中使用等。
存储地址
所有 android 联系信息都保存在 SQLite 数据库中。 数据库文件位于 /data/data/com.android.providers.contacts/databases/contacts2.db
。
我们可以使用 android模拟器上查看上述数据库文件。 如果无法打开上述文件夹,请在 dos 或 shell 窗口中运行 adb root shell 命令。
adb devices
adb shell
su
sqlite3 /data/data/com.android.providers.contactsdatabases/contacts2.db
select * from sqlite_master where type="table"; // 我们可以先查看这个数据库里面有哪些表,太多了
select * from sqlite_master where type="table" and name = "data"; // 查看data表的结构
我们可以看到当时这个表是这么创建的
CREATE TABLE data (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
package_id INTEGER REFERENCES package(_id),
mimetype_id INTEGER REFERENCES mimetype(_id) NOT NULL,
raw_contact_id INTEGER REFERENCES raw_contacts(_id) NOT NULL,
hash_id TEXT,
is_read_only INTEGER NOT NULL DEFAULT 0,
is_primary INTEGER NOT NULL DEFAULT 0,
is_super_primary INTEGER NOT NULL DEFAULT 0,
data_version INTEGER NOT NULL DEFAULT 0,
data1 TEXT,data2 TEXT,data3 TEXT,data4 TEXT,data5 TEXT,data6 TEXT,data7 TEXT,data8 TEXT,data9 TEXT,data10 TEXT,data11 TEXT,data12 TEXT,data13 TEXT,data14 TEXT,data15 TEXT,
data_sync1 TEXT, data_sync2 TEXT, data_sync3 TEXT, data_sync4 TEXT,
carrier_presence INTEGER NOT NULL DEFAULT 0,
preferred_phone_account_component_name TEXT,
preferred_phone_account_id TEXT
)
其中有
data1 ~ data15
,data_sync1 ~ data_sync4
其实对于我们常用的就是mimetype_id
、raw_contact_id
、data1
、data2
、data3
,数据的类型就是mimetype_id
,数据的内容就是data1
,而data2
、data3
则是用来辅助data1
,如果mimetype_id
对应的是电话号码这组数据,data1
就是电话号码,而data2
就是电话类型(比如手机,座机)、data3
就是自定义的电话类型(比如data2中的分类都不满足,你自定义的紧急电话类型)
数据库中的几个我们能用的表都是依靠相同的raw_contact_id
连接起来的。
select * from sqlite_master where type="table" and name = "mimetypes"; // 查看data表的结构
该表是这么创建的
CREATE TABLE mimetypes (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
mimetype TEXT NOT NULL
)
举个例子
select _id, raw_contact_id,mimetype_id, data1, data2, data3 from data order by _id desc limit 20;
红框中的1267就是一个raw_contact_id
,可以看到这里的6条记录都是我们Test这个联系人的数据:
第一行mimetype_id
为5,可以看出是手机号码
第四行mimetype_id
为7,可以看出是名字
第六行mimetype_id
为13,可以看出是事件,这里存档是日期,根据后面的data2为3可以区分出是生日。
其它的几个是空的,对应的可以根据上面的mimetypes
找到对应的含义。
实战
知道了数据的存储结构了,我们就可以开始查询了
我们可用一个fragment来展示联系人列表数据,fragment同时继承接口LoaderManager.LoaderCallbacks<cursor>
lateinit var contactsList: ListView
// Defines a variable for the search string
private val searchString: String = ""
private var lookupKey: String = ""
// An adapter that binds the result Cursor to the ListView
private var cursorAdapter: SimpleCursorAdapter? = null
private val detailSelectionArgs = arrayOf<String>("")
private val selectionArgs = arrayOf<String>(searchString)
// 联系人姓名兼容老版本
private val DISPLAY_NAME: String = if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
} else {
ContactsContract.Contacts.DISPLAY_NAME
}
// 类似于mysql select 对应的数据列
private val PROJECTION: Array<out String> = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY,
DISPLAY_NAME,
ContactsContract.Contacts.Data.DATA1
)
// 类似于mysql的where语句
private val SELECTION: String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
"${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
else
"${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
// cursor数据对应列
private val FROM_COLUMNS: Array<String> = arrayOf(
DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.Contacts._ID
)
// cursor数据对应列对应的展示位置ID
private val TO_IDS: IntArray = intArrayOf(R.id.msgTime, R.id.msgTitle, R.id.msgBody)
private var DETAIL_SELECTION: String =
ContactsContract.Data.LOOKUP_KEY + " = ? AND " +
ContactsContract.Data.MIMETYPE + " IN ('" + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + "', '" + ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE + "', '" + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE + "', '" + ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE + "')"
private val DETAIL_PROJECTION: Array<out String> = arrayOf(
ContactsContract.Contacts.Data._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.Data.MIMETYPE,
ContactsContract.Contacts.Data.DATA1,
ContactsContract.Contacts.Data.DATA2,
ContactsContract.Contacts.Data.DATA3,
)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentContactListBinding.inflate(inflater, container, false)
val root: View = binding.root
contactsList = root.findViewById(R.id.contact_list)
textView = root.findViewById(R.id.contact_list_text)
textView?.text = "loading..."
self = this
// initDataFromMock();
activity?.also {
// Gets a CursorAdapter
cursorAdapter = SimpleCursorAdapter(
it,
R.layout.msg_item, // ListView对应的layout xml文件
null,
FROM_COLUMNS,
TO_IDS,
0
)
// Sets the adapter for the ListView
contactsList.adapter = cursorAdapter
}
// Initializes the loader
LoaderManager.getInstance(this).initLoader(2, null, this)
return root
}
override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
/*
* Makes search string into pattern and
* stores it in the selection array
*/
loaderFlag = id
selectionArgs[0] = "%$searchString%"
val mLoader = activity?.let {
CursorLoader(
it,
ContactsContract.Data.CONTENT_URI,
PROJECTION,
SELECTION,
selectionArgs,
SORT_ORDER
)
}
// Starts the query
return mLoader ?: throw IllegalStateException()
}
}
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
// Put the result Cursor in the adapter for the ListView
cursorAdapter?.swapCursor(cursor)
// 用进程打印列表数据
Thread {
val userList: ArrayList<User> = arrayListOf();
// 因为列表的查询没有指定搜索条件(searchString为空),查询的是全部数据,那样每个联系会出来好几条数据,可以简单用个hashMap去重下
var userHashMap: HashMap<String, User> = HashMap();
try {
while (cursor.moveToNext()) {
val contactId = cursor.getString(0)
val contactKey = cursor.getString(1)
val contactName = cursor.getString(2)
var currentInfos: MutableList<String?> = ArrayList()
currentInfos.add("contactName = $contactName")
var userPhoneList : ArrayList<UserPhone> = arrayListOf();
var userEventList : ArrayList<UserEvent> = arrayListOf();
// 我们可以用类似的方式查询该联系对应的具体数据
val cr: ContentResolver = requireActivity().contentResolver
detailSelectionArgs[0] = contactKey!!
val curs: Cursor? = cr.query(ContactsContract.Data.CONTENT_URI,
DETAIL_PROJECTION,
DETAIL_SELECTION,
detailSelectionArgs,
null)
if (curs != null) {
while (curs.moveToNext()) {
val id: Long = cursor.getLong(0)
val name: String = curs.getString(1) // full name
val mime: String = curs.getString(2) // type of data (phone / birthday / email)
val data: String? = curs.getString(3) // the actual info, e.g. +1-212-555-1234
var type: String? = curs.getString(4);
var label: String? = curs.getString(5);
var kind = "unknown"
when (mime) {
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> {
kind = "phone"
userPhoneList.add(UserPhone(contactKey, data, type, label))
}
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE -> {
kind = "event"
userEventList.add(UserEvent(contactKey, data, type, label))
}
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> kind = "email"
ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE -> kind = "note"
}
if (data != null) {
currentInfos!!.add("$kind = $data/$type/$label")
}
}
Log.i("CURRENT", contactName + "_" + contactId + "_" + currentInfos.toString())
}
val user = User(contactKey, contactId, contactName, userPhoneList, userEventList, null);
if (!userHashMap.containsKey(user.key) && userEventList.size > 0) {
userHashMap.put(user.key, user);
userList.add(User(contactKey, contactId, contactName, userPhoneList, userEventList, null))
}
}
} catch (e: Exception) {
Log.e("ABC", "while error $e")
}
Log.i("EFG", userList.toString())
if (userList.size > 0) {
val userListStr = Gson().toJson(userList);
Utils.saveData(requireContext(), "test", "userList", userListStr)
// textView?.text = "list: " + userList.toString()
Log.i("LIST", userList.toString())
}
}.start()
}
代码有所截取,可能有部分变量什么的遗漏的,但是示意是足够了。
本文启发自
- 分类:
- Android