Android搜索本地應用、聯(lián)系人换衬、本地文件

效果演示:

列表采用一個recyclerview實現(xiàn)痰驱,定義一個公共父實體類,定義基礎屬性瞳浦,各個item類型的實體類擁有自己的屬性担映,繼承公共父實體類。然后adapter采用多itemType叫潦,多viewHolder處理蝇完。

Adapter代碼:
class MutipleAdapter(val context: Context): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var itemList = CopyOnWriteArrayList<AdapterItem>()

    fun appendDatas(datas: List<AdapterItem>): MutipleAdapter {
        this.itemList.addAll(datas)
        notifyDataSetChanged()
        return this
    }

    fun appendData(data: AdapterItem): MutipleAdapter {
        this.itemList.add(data)
        return this
    }

    fun clearData() {
        itemList.clear()
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return if (viewType == ITEM_TYPE_FILE) {
            ViewHolderFile(ItemFileBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        } else if (viewType == ITEM_TYPE_CONTACT) {
            ViewHolderContact(ItemContactBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        } else if (viewType == ITEM_TYPE_App) {
            ViewHolderApp(ItemAppBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        } else {
            ViewHolderHeader(ItemHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        }
    }

    override fun getItemCount() = itemList.size

    override fun getItemViewType(position: Int): Int {
        return itemList[position].itemType
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        var itemData = itemList[position]
        if (holder is ViewHolderHeader) {
            holder.bind(itemData)
        } else if (holder is ViewHolderFile) {
            holder.bind(itemData)
        } else if (holder is ViewHolderContact) {
            holder.bind(itemData)
        } else if (holder is ViewHolderApp) {
            holder.bind(itemData)
        }
    }

    inner class ViewHolderHeader(val binding: ItemHeaderBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: AdapterItem) {
            binding.tvHeaderTitle.text = item.headerTitle
        }
    }

    inner class ViewHolderFile(val binding: ItemFileBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: AdapterItem) {
            if (item is FileBean) {
                binding.tvFileName.text = item.fileName
                binding.tvFileInfo.text = "" + (item.fileSize/1000) + "KB"

                setFileIcon(binding.ivIcon, item.fileName)
            }
        }
    }

    inner class ViewHolderContact(val binding: ItemContactBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: AdapterItem) {
            if (item is ContactBean) {
                binding.tvName.text = item.name
                binding.tvNumber.text = item.number
                binding.tvCall.setOnClickListener {
                    callPhone(item.number)
                }
            }
        }
    }

    inner class ViewHolderApp(val binding: ItemAppBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: AdapterItem) {
            if (item is AppBean) {
                binding.tvAppName.text = item.name
                item.intent?.let {
                    binding.ivIcon.background = context.packageManager.getActivityIcon(it)
                }
            }
        }
    }

    /**
     * 根據(jù)文件后綴類型設置對應類型圖標
     */
    private fun setFileIcon(imageView: ImageView, fileName: String) {
        if (fileName.endsWith(".jpg") || fileName.endsWith(".mp4")) {
            imageView.background = context.resources.getDrawable(R.drawable.category_file_icon_pic_phone)
        } else {
            var drawableId = 0
            if (fileName.endsWith(".txt") || fileName.endsWith(".pdf")) {
                drawableId = R.drawable.category_file_icon_doc_phone
            } else if (fileName.endsWith(".zip")) {
                drawableId = R.drawable.category_file_icon_zip_phone
            } else if (fileName.endsWith(".mp3")) {
                drawableId = R.drawable.category_file_icon_music_phone
            } else if (fileName.endsWith(".apk")) {
                drawableId = R.drawable.category_file_icon_apk_phone
            } else {
                drawableId = R.drawable.ic_local_file
            }
            imageView.background = context.resources.getDrawable(drawableId)
        }
    }

    /**
     * 撥打電話
     */
    fun callPhone(phoneNumber: String) {
        var intent = Intent(Intent.ACTION_CALL)
        var data = Uri.parse("tel:$phoneNumber")
        intent.data = data
        context.startActivity(intent)
    }

}

各個實體類代碼:

open class AdapterItem(var itemType: Int = 0, val headerTitle: String = "")

class AppBean(val pkg: String, val icon: Int, val name: String, val intent: Intent?): AdapterItem() {

    init {
        itemType = ItemType.ITEM_TYPE_App
    }
}

class ContactBean(val name: String, val number: String): AdapterItem() {

    init {
        itemType = ItemType.ITEM_TYPE_CONTACT
    }
}

data class FileBean(val fileName: String, val path: String, val fileSize: Int): AdapterItem() {

    init {
        itemType = ITEM_TYPE_FILE
    }
}

獲取本地應用:

object SearchAppProvider {
    fun searchInstallApps(context: Context): List<AppBean>? {
        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            checkInstallAppsBeforeL(context)
        } else {
            checkInstallAppsAfterL(context)
        }
    }

    private fun checkInstallAppsBeforeL(context: Context): List<AppBean> {
        val apps: MutableList<AppBean> = ArrayList()
        val pm = context.packageManager
        try {
            val packageInfos = pm.getInstalledPackages(0)
            for (i in packageInfos.indices) {
                val pkgInfo = packageInfos[i]
                val AppBean = pkgInfo.applicationInfo
                if (TextUtils.equals(context.packageName, pkgInfo.packageName)) continue
                val intent = getLaunchIntent(pm, pkgInfo.packageName)
                intent!!.flags =
                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP
                val app = AppBean(
                    pkgInfo.packageName,
                    AppBean.icon,
                    AppBean.loadLabel(pm).toString(),
                    intent
                )
                apps.add(app)
            }
        } catch (e: Exception) {
            //
        }


        return apps
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private fun checkInstallAppsAfterL(context: Context): List<AppBean>? {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null
        val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
            ?: return null
        val apps: MutableList<AppBean> = ArrayList()
        try {
            val activityInfos = launcherApps.getActivityList(null, Process.myUserHandle())
            for (activityInfo in activityInfos) {
                val AppBean = activityInfo.applicationInfo
                val intent = Intent(Intent.ACTION_MAIN)
                intent.addCategory(Intent.CATEGORY_LAUNCHER)
                intent.setPackage(AppBean.packageName)
                intent.component = activityInfo.componentName
                intent.flags =
                    Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP
                val app = AppBean(
                    AppBean.packageName,
                    AppBean.icon,
                    activityInfo.label.toString(),
                    intent
                )
                apps.add(app)
            }
        } catch (e: Exception) {
        }
        return apps
    }

    private fun getLaunchIntent(pm: PackageManager, pkg: String): Intent? {
        var intent = pm.getLaunchIntentForPackage(pkg)
        return if (intent != null) {
            intent
        } else {
            intent = Intent(Intent.ACTION_MAIN)
            intent.addCategory(Intent.CATEGORY_LAUNCHER)
            intent.setPackage(pkg)
            val apps = pm.queryIntentActivities(intent, 0)
            if (apps == null || apps.isEmpty()) {
                return null
            }
            val ri = apps.iterator().next() ?: return null
            intent.component = ComponentName(pkg, ri.activityInfo.name)
            intent
        }
    }
}

模糊查詢聯(lián)系人:

object SearchContactProvider {

    @SuppressLint("Range")
    fun readContacts(context: Context) {
        //ContactsContract.CommonDataKinds.Phone 聯(lián)系人表
        var cursor: Cursor? = context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null, null, null, null)
        cursor?.let {
            while (it.moveToNext()) {
                //讀取通訊錄的姓名
                var name = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                //讀取通訊錄的號碼
                var number = cursor.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                Log.i("minfo", "$name--$number")
            }
        }
    }

    /**
     * 模糊查詢聯(lián)系人
     */
    @SuppressLint("Range")
    fun searchContact(context: Context, key: String): List<ContactBean> {
        //ContactsContract.CommonDataKinds.Phone 聯(lián)系人表
        var list = ArrayList<ContactBean>()
        val projection = arrayOf(
            ContactsContract.PhoneLookup.DISPLAY_NAME,
            ContactsContract.CommonDataKinds.Phone.NUMBER
        )
        val selection = StringBuilder()
        selection.append(ContactsContract.Contacts.DISPLAY_NAME)
        selection.append(" LIKE '%$key%' ")
        var cursor: Cursor? = context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            projection, selection.toString(), null, null)
        cursor?.let {
            while (it.moveToNext()) {
                //讀取通訊錄的姓名
                var name = it.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                //讀取通訊錄的號碼
                var number = cursor.getString(it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
                Log.i("minfo", "$name--$number")
                list.add(ContactBean(name, number))
            }
            it.close()
        }
        return list
    }
}

模糊查詢本地文件:

/**
 * 使用contentResolver查詢本地各種文件
 */
object SearchFileProvider {
    private const val MAX_FILE_COUNT = 20

    /**
     * 模糊查詢本地文件
     */
    suspend fun searchLocalFile(context: Context, key: String): List<FileBean> {
        var list = ArrayList<FileBean>()
        val volumeName = "external"
        val columns = arrayOf(MediaStore.Files.FileColumns.DATA)
        val selection = MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.mp3' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.json' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.log' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.apk' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.mp4' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.pdf' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.txt' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.jpg' OR " +
                MediaStore.Files.FileColumns.DATA + " LIKE '%$key%.zip'"
        var cursor: Cursor? = null
        try {
            cursor = context.contentResolver.query(
                MediaStore.Files.getContentUri(volumeName),
                columns,
                selection,
                null,
                null
            )
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    if (list.size < MAX_FILE_COUNT) {
                        val absolutePath = cursor.getString(0)
                        File(absolutePath).apply {
                            if (exists() && !TextUtils.isEmpty(name) && name.contains(".")) {
                                if (!TextUtils.isEmpty(name)) {
                                    var bean = FileBean(name, path, readBytes().size)
                                    list.add(bean)
                                }
                            }
                        }
                    } else {
                        return list
                    }
                }
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        } finally {
            try {
                if (cursor != null) {
                    cursor.close()
                    cursor = null
                }
            } catch (e: java.lang.Exception) {
            }
        }
        return list
    }
}

異步加載各類數(shù)據(jù),再使用async/await同步使用數(shù)據(jù)結(jié)果

    /**
     * 搜索各類數(shù)據(jù)
     */
    private fun loadData(key: String) {
        adapter.clearData()

        //搜索本地App
        val localAppsDeferred = GlobalScope.async(Dispatchers.IO) {
            SearchAppProvider.searchInstallApps(applicationContext)
        }

        //搜索聯(lián)系人
        val contactsDeferred = GlobalScope.async(Dispatchers.IO) {
            SearchContactProvider.searchContact(applicationContext, key)
        }

        //搜索本地文件
        val localFilesDeferred = GlobalScope.async(Dispatchers.IO) {
            SearchFileProvider.searchLocalFile(applicationContext, key)
        }

        GlobalScope.launch {
            // 通過 await 獲取異步任務的結(jié)果
            val localApps = localAppsDeferred.await()
            val contacts = contactsDeferred.await()
            val localFiles = localFilesDeferred.await()

            withContext(Dispatchers.Main) {
                adapter.appendData(AdapterItem(0, "本機應用"))
                adapter.appendDatas(localApps!!.take(10))

                adapter.appendData(AdapterItem(0, "聯(lián)系人"))
                    .appendDatas(contacts)

                adapter.appendData(AdapterItem(0, "文件管理")).appendDatas(localFiles)  //先添加內(nèi)容的header诅挑,再添加內(nèi)容
            }
        }
    }

然后四敞,需要能夠訪問文件泛源,別忘了加上6.0訪問權限拔妥,獲取本地文件、讀取聯(lián)系人訪問的權限达箍。并且注意没龙,在targetsdk 29及以下,可以訪問所有問題缎玫,高于29硬纤,則只能夠訪問到圖片,視頻赃磨,音樂這樣的多媒體文件筝家。


        requestPermissions(arrayOf(Manifest.permission.READ_CONTACTS,
            Manifest.permission.CALL_PHONE, Manifest.permission.WRITE_EXTERNAL_STORAGE), this)

Github 代碼地址:

https://github.com/running-libo/SearchLocalData

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市邻辉,隨后出現(xiàn)的幾起案子溪王,更是在濱河造成了極大的恐慌腮鞍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莹菱,死亡現(xiàn)場離奇詭異移国,居然都是意外死亡,警方通過查閱死者的電腦和手機道伟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門迹缀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜜徽,你說我怎么就攤上這事祝懂。” “怎么了拘鞋?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵嫂易,是天一觀的道長。 經(jīng)常有香客問我掐禁,道長怜械,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任傅事,我火速辦了婚禮缕允,結(jié)果婚禮上婆誓,老公的妹妹穿的比我還像新娘囤官。我一直安慰自己昏兆,他們只是感情好猎唁,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布稀轨。 她就那樣靜靜地躺著苇侵,像睡著了一般耕餐。 火紅的嫁衣襯著肌膚如雪店枣。 梳的紋絲不亂的頭發(fā)上买置,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天粪糙,我揣著相機與錄音,去河邊找鬼忿项。 笑死蓉冈,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的轩触。 我是一名探鬼主播寞酿,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脱柱!你這毒婦竟也來了伐弹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤榨为,失蹤者是張志新(化名)和其女友劉穎惨好,沒想到半個月后椅邓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡昧狮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年景馁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逗鸣。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡合住,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出撒璧,到底是詐尸還是另有隱情透葛,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布卿樱,位于F島的核電站僚害,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏繁调。R本人自食惡果不足惜萨蚕,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹄胰。 院中可真熱鬧岳遥,春花似錦、人聲如沸裕寨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宾袜。三九已至捻艳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庆猫,已是汗流浹背认轨。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阅悍,地道東北人好渠。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓昨稼,卻偏偏與公主長得像节视,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子假栓,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容