4

Android摸索记录--获取android联系人

 1 year ago
source link: https://www.daozhao.com/10462.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android摸索记录--获取android联系人

如果您发现本文排版有问题,可以先点击下面的链接切换至老版进行查看!!!

Android摸索记录--获取android联系人

既然要做提醒、通知类的app,首先想尝试的就是支持自己手机通讯录的生日提醒。

这里说的android联系人,指的是存储在安卓手机google账户下面的联系人,而不是存储在本机或者某个国产手机系统的联系人。

android联系人

联系人的数据大致分为16类,分别是

  • 电子邮件(地址、电子邮件类型(家庭、工作、手机、其他))。
  • 即时消息(协议(qq、icq、skype 等)、im id)。
  • 组织(公司、部门、职务、职位描述、办公地点)。
  • 电话(号码、电话类型(家庭、手机、工作、其他))。
  • sip地址
  • 姓名(显示名称、名字、姓氏)。
  • 邮政地址(国家、城市、地区、街道、邮政编码)。
  • 身份(命名空间(SSN、护照)、号码)。
  • 组(联系人属于组 id)。
  • 网站(网站 url,网站类型())。
  • 事件(生日、其它自定义事件)

对于某些组,例如电子邮件、电话、地址等。信息还根据数据使用情况进行分类,例如在家中使用、在工作中使用等。

所有 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()
    }

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

本文启发自


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK