道招

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

file

select * from sqlite_master where type="table";  // 我们可以先查看这个数据库里面有哪些表,太多了
select * from sqlite_master where type="table" and name = "data"; // 查看data表的结构

我们可以看到当时这个表是这么创建的 file

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 ~ data15data_sync1 ~ data_sync4

其实对于我们常用的就是mimetype_idraw_contact_id data1 data2data3,数据的类型就是mimetype_id,数据的内容就是data1,而data2data3则是用来辅助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
)

file

举个例子

select _id, raw_contact_id,mimetype_id, data1, data2, data3 from data order by _id desc limit 20;

file

红框中的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()
    }

代码有所截取,可能有部分变量什么的遗漏的,但是示意是足够了。

本文启发自

更新时间:
上一篇:PWA缓存控制和版本升级实践下一篇:Android摸索记录--设置生日提醒

相关文章

关注道招网公众帐号
友情链接
消息推送
道招网关注互联网,分享IT资讯,前沿科技、编程技术,是否允许文章更新后推送通知消息。
允许
不用了