当前位置: 首页 > 知识库问答 >
问题:

如何在Android 11上使用Media Store API获取隐藏文件夹中的文件

叶稳
2023-03-14

我需要获取外部存储上WhatsApp文件夹中的数据。由于我的目标是API级别30,我不再能够访问外部存储上的WhatsApp文件夹。我已经实现了存储访问框架,获得了Android/media文件夹和文档文件。使用listFiles()我可以列出文件,但使用filter()sortedbydescing()函数,速度会变得非常慢。

我试过什么?

>

它返回隐藏文件夹的空光标。状态

尝试替换MediaStore.Video.Media。带有MediaStore.Video.Media.getContentUri(MediaStore.VOLUME\u EXTERNAL)

需要什么?

  • 列出图像和视频。状态文件夹与我在HomeA中使用媒体商店列出WhatsApp图像相同ctivity.java

下面是我的代码

class HomeActivity : AppCompatActivity(), InternetListener, PurchasesUpdatedListener,
    CoroutineScope {
    private val exceptionHandler = CoroutineExceptionHandler { context, exception ->
        Toast.makeText(this, exception.message, Toast.LENGTH_LONG).show()

    }
    private val dataRepository: DataRepository by inject()
    val tinyDB: TinyDB by inject()

    val REQUEST_CODE = 12123

    init {
        newNativeAdSetUp = null
    }

    val sharedViewModel by viewModel<SharedViewModel>()

    val viewModel by viewModel<HomeViewModel>()


    val handler = CoroutineExceptionHandler { _, exception ->
        Log.d("CoroutineException", "$exception handled !")
    }
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job + handler
    private lateinit var job: Job
    val sdk30PermissionListener = object : PermissionListener {
        override fun onPermissionGranted() {
            openDocumentTree()
        }

        override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        handlePermissionsByVersion()
    }

    private fun handlePermissionsByVersion() {

        if (SDK_INT >= Build.VERSION_CODES.R) {
            if ((ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                )
                        == PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.READ_EXTERNAL_STORAGE
                )
                        == PackageManager.PERMISSION_GRANTED)
            ) {
                //if granted load whatsapp images and some uris setup to viewmodel
                loadWhatsAppImages()
                if (arePermissionsGranted()) {
                    if (dataRepository.mrWhatsAppImages == null || dataRepository.mrWhatsAppBusinessImages == null) {
                        setUpWAURIs()
                    }
                }
            } else {
                TedPermission.with(this)
                    .setPermissionListener(sdk30PermissionListener)
                    .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
                    .setPermissions(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE
                    )
                    .check()
            }

        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)


        if (resultCode == RESULT_OK && requestCode == REQUEST_CODE) {
            if (data != null) {
                //this is the uri user has provided us
                val treeUri: Uri? = data.data
                if (treeUri != null) {
                    sharedViewModel.treeUri = treeUri
                    val decoded = Uri.decode(treeUri.toString())
                    Log.i(LOGTAG, "got uri: ${treeUri.toString()}")
                    // here we should do some checks on the uri, we do not want root uri
                    // because it will not work on Android 11, or perhaps we have some specific
                    // folder name that we want, etc
                    if (Uri.decode(treeUri.toString()).endsWith(":")) {
                        showWrongFolderSelection()
                        return
                    }
                    if (!decoded.equals(Constants.WHATSAPP_MEDIA_URI_DECODED)) {
                        showWrongFolderSelection()
                        return
                    }
                    // here we ask the content resolver to persist the permission for us
                    val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    contentResolver.takePersistableUriPermission(
                        treeUri,
                        takeFlags
                    )
                    val treeUriAsString = treeUri.toString()
                    tinyDB.putString("FOLDER_URI", treeUriAsString)
                    if (SDK_INT >= Build.VERSION_CODES.R) {
                        setupPaths()
                    }

                }
            }
        }
    }

    private fun setupPaths() {
        setUpOverlay()
        fetchWhatsAppRootURIs(
            this,
            sharedViewModel,
            dataRepository,
            tinyDB
        ) {
            fetchWhatsAppBusinessRootURIs(
                this,
                sharedViewModel,
                dataRepository,
                tinyDB
            ) {
                tinyDB.putBoolean("WARootPathsDone", true)
                removeOverlay()
            }
        }


    }

    override fun onDestroy() {
        dialogHandler.removeCallbacksAndMessages(null)
        super.onDestroy()
    }
    val loadmanagerImages = object : LoaderManager.LoaderCallbacks<Cursor> {
        val whatsAppImagesArrayList = arrayListOf<File>()


        override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
            var location: File = File(
                Environment.getExternalStorageDirectory()
                    .toString() + Constants.whatsapp_images_path
            )
            if (!location.exists()) {
                location = File(
                    Environment.getExternalStorageDirectory()
                        .toString() + Constants.whatsapp_images_path11
                )
            }

            if (location != null && location.exists()) {
                whatsAppImagesArrayList.clear()
                Timber.e("checkLoaded-onCreateLoader $id")
                if (id == 0) {
                    var folder = location.absolutePath
                    val projection = arrayOf(
                        MediaStore.MediaColumns.DATA,
                        MediaStore.MediaColumns.DATE_MODIFIED
                    )
                    val selection = MediaStore.Images.Media.DATA + " like ? "
                    val selectionArgs: String = "%$folder%"

                    return CursorLoader(
                        this@HomeActivity,
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        projection,
                        selection,
                        arrayOf(selectionArgs),
                        "${MediaStore.Images.Media.DATE_MODIFIED} DESC"
                    )
                }
            }

            return null!!
        }

        override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
            Timber.e("checkLoaded-onLoadFinished")
            var absolutePathOfImage: String
            if (loader.id == 0) {
                cursor?.let {
                    val columnIndexData = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
                    GlobalScope.launch(Dispatchers.Main + exceptionHandler) {

                        async(Dispatchers.IO + exceptionHandler) {
                            while (!cursor.isClosed && cursor.moveToNext() == true) {
                                absolutePathOfImage = cursor.getString(columnIndexData!!)
                                whatsAppImagesArrayList.add(File(absolutePathOfImage))

                            }
                        }.await()
                        LoaderManager.getInstance(this@HomeActivity).destroyLoader(0)
                        Timber.e("checkLoaded-Completion")
                        galleryViewModel.whatsAppImagesList.postValue(whatsAppImagesArrayList)
                    }


                }
            }
        }

        override fun onLoaderReset(loader: Loader<Cursor>) {
        }

    }

    fun loadWhatsAppImages() {
        try {
            tinyDB.putBoolean("whatsAppMediaLoadCalled", true)
            LoaderManager.getInstance(this).initLoader(
                0,
                null,
                loadmanagerImages
            )
        } catch (e: RuntimeException) {
            Log.e("exVideos ", "ex : ${e.localizedMessage}")
        }

    }


    companion object {
        const val ANDROID_DOCID = "primary:Android/media/"
        const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
        private val androidUri = DocumentsContract.buildDocumentUri(
            EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
        )
        val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
            EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
        )
    }

    private fun openDocumentTree() {
        val uriString = tinyDB.getString("FOLDER_URI", "")
        when {
            uriString == "" -> {
                Log.w(LOGTAG, "uri not stored")
                askPermission()
            }
            arePermissionsGranted() -> {
            }
            else -> {
                Log.w(LOGTAG, "uri permission not stored")
                askPermission()
            }
        }
    }
    // this will present the user with folder browser to select a folder for our data
    private fun askPermission() {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, androidUri)
        startActivityForResult(intent, REQUEST_CODE)
    }

    private fun arePermissionsGranted(): Boolean {
        var uriString = tinyDB.getString("FOLDER_URI", "")
        val list = contentResolver.persistedUriPermissions
        for (i in list.indices) {
            val persistedUriString = list[i].uri.toString()
            if (persistedUriString == uriString && list[i].isWritePermission && list[i].isReadPermission) {
                return true
            }
        }
        return false
    }

    private fun showWrongFolderSelection() {
        val layoutInflaterAndroid = LayoutInflater.from(this)
        val mView = layoutInflaterAndroid.inflate(R.layout.layout_dialog_wrong_folder, null)
        val builder = AlertDialog.Builder(this, R.style.ThemePageSearchDialog)
        builder.setView(mView)
        val alertDialog = builder.show()
        alertDialog.setCancelable(false)
        val btnOk = mView.findViewById(R.id.tvExit) as TextView
        val tvCancel = mView.findViewById(R.id.tvCancel) as TextView
        btnOk.setOnClickListener {
            alertDialog.dismiss()
            openDocumentTree()
        }
        tvCancel.setOnClickListener {
            alertDialog.dismiss()
        }

    }

    private fun setUpWAURIs() {
        dataRepository.mrWhatsAppImages =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppImages")
            )
        dataRepository.mrWhatsAppVN =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppVN")
            )
        dataRepository.mrWhatsAppDocs =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppDocs")
            )
        dataRepository.mrWhatsAppVideo =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppVideo")
            )
        dataRepository.mrWhatsAppAudio =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppAudio")
            )
        dataRepository.WhatsAppStatuses =
            getDocumentFileFromStringURIStatuses(
                this,
                tinyDB.getString("WhatsAppStatuses")
            )



        dataRepository.mrWhatsAppBusinessImages =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppBusinessImages")
            )
        dataRepository.mrWhatsAppBusinessVN =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppBusinessVN")
            )
        dataRepository.mrWhatsAppBusinessDocs =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppBusinessDocs")
            )
        dataRepository.mrWhatsAppBusinessVideo =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppBusinessVideo")
            )
        dataRepository.mrWhatsAppBusinessAudio =
            getDocumentFileFromStringURI(
                this,
                tinyDB.getString("mrWhatsAppBusinessAudio")
            )
        dataRepository.WhatsAppBusinessStatuses =
            getDocumentFileFromStringURIStatuses(
                this,
                tinyDB.getString("WhatsAppBusinessStatuses")
            )
    }

    fun setUpOverlay() {
        val dialogfragment = FullScreenLoadingDialog()
        dialogfragment.isCancelable = false
        dialogfragment.setisAdmobAd(true)
        val ft: FragmentTransaction =
            supportFragmentManager.beginTransaction()
        ft.add(dialogfragment, "DialogFragment_FLAG")
        ft.commitAllowingStateLoss()
    }

    fun removeOverlay() {
        val fragment: Fragment? = supportFragmentManager.findFragmentByTag("DialogFragment_FLAG")
        if (fragment != null && fragment is DialogFragment) {
            fragment.dismissAllowingStateLoss()
        }
    }
    fun fetchWhatsAppRootURIs(
        context: Context,
        sharedViewModel: SharedViewModel,
        dataRepository: DataRepository,
        tinyDB: TinyDB, completed: () -> Unit

    ) {
        val selectedPackageName = Constants.WHATSAPP_PKG_NAME
        val selectedRootName = Constants.WHATSAPP_ROOT_NAME
        var waImages: DocumentFile? = null
        var waVN: DocumentFile? = null
        var waDocs: DocumentFile? = null
        var waVideos: DocumentFile? = null
        var waAudio: DocumentFile? = null
        var waStatus: DocumentFile? = null
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && sharedViewModel.treeUri != null) {
            CoroutineScope(Dispatchers.Main).launch {
                async(Dispatchers.IO) {
                    val dir = DocumentFile.fromTreeUri(
                        context,
                        sharedViewModel.treeUri!!
                    )
                    dir?.listFiles()?.forEach {
                        if (it.name.equals(selectedPackageName)) {
                            it.listFiles().forEach {
                                if (it.name.equals(selectedRootName)) {
                                    it.listFiles().forEach {
                                        if (it.name.equals(Constants.WHATSAPP_MEDIA_FOLDER_NAME)) {
                                            it.listFiles().forEach {
                                                if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_IMAGES)) {
                                                    waImages = it

                                                } else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_VN)) {
                                                    waVN = it

                                                } else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_DOCUMENTS)) {
                                                    waDocs = it
                                                } else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_VIDEO)) {
                                                    waVideos = it

                                                } else if (it.name.equals(Constants.FOLDER_NAME_WHATSAPP_AUDIO)) {
                                                    waAudio = it

                                                } else if (it.name.equals(Constants.FOLDER_NAME_STATUSES)) {
                                                    waStatus = it

                                                }

                                            }


                                        }
                                    }
                                }
                            }
                        }


                    }
                }.await()
                Timber.e("processStatusFetch:Done")
                tinyDB.putString("mrWhatsAppImages", waImages?.uri.toString())
                tinyDB.putString("mrWhatsAppVN", waImages?.uri.toString())
                tinyDB.putString("mrWhatsAppDocs", waImages?.uri.toString())
                tinyDB.putString("mrWhatsAppVideo", waImages?.uri.toString())
                tinyDB.putString("mrWhatsAppAudio", waImages?.uri.toString())
                tinyDB.putString("WhatsAppStatuses", waStatus?.uri.toString())

                dataRepository.mrWhatsAppImages = waImages
                dataRepository.mrWhatsAppVN = waVN
                dataRepository.mrWhatsAppDocs = waDocs
                dataRepository.mrWhatsAppVideo = waVideos
                dataRepository.mrWhatsAppAudio = waAudio
                dataRepository.WhatsAppStatuses = waStatus
                completed()


            }
        }
    }
class StatusImageFragment : Fragment(), StatusListener, CoroutineScope {

    companion object {
        fun newInstance() = StatusImageFragment()
    }

    val handler = CoroutineExceptionHandler { _, exception ->
        Log.d("CoroutineException", "$exception handled !")
    }
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job + handler
    private lateinit var job: Job

    private var adapterSDK30 = StatusImageAdapterSDK30()
    private var no_image: ImageView? = null
    private var no_image_txt: TextView? = null

    val tinyDB: TinyDB by inject()
    val sharedViewModel by viewModel<SharedViewModel>()
    private val dataRepository: DataRepository by inject()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        job = Job()
        return inflater.inflate(R.layout.status_image_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        swipeRefresh(false, false)
    }


    public fun swipeRefresh(isReloadRequired: Boolean, isFromModeChanged: Boolean) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                if (isFromModeChanged) {
                    status_image_recycler.visibility = View.GONE
                    progressbar.visibility = View.VISIBLE
                    no_image?.let {
                        it.visibility = View.GONE
                    }
                    no_image_txt?.let {
                        it.visibility = View.GONE
                    }
                    go_to_app?.let {
                        it.visibility = View.GONE
                    }
                } else {
                    if (adapterSDK30.listImages == null || adapterSDK30.listImages.size == 0) {
                        no_image?.let {
                            it.visibility = View.GONE
                        }
                        no_image_txt?.let {
                            it.visibility = View.GONE
                        }
                        go_to_app?.let {
                            it.visibility = View.GONE
                        }
                        progressbar.visibility = View.VISIBLE
                    }
                }
                if (isReloadRequired) {
                    processStatusFetchFromChild({
                        sharedViewModel.statusImages.observe(viewLifecycleOwner, Observer {
                            val arrayList = it
                            adapterSDK30.listImages = arrayList
                            postFetchingExecutionSDK30()
                        })
                    })

                } else {
                    sharedViewModel.statusImages.observe(viewLifecycleOwner, Observer {
                        val arrayList = it
                        adapterSDK30.listImages = arrayList
                        adapterSDK30.listImages = it
                        postFetchingExecutionSDK30()
                    })
                }

            }
        } catch (ex: Exception) {
            ex.printStackTrace()

        }

    }

    private fun postFetchingExecutionSDK30() {
        progressbar.visibility = View.GONE
        status_image_recycler.visibility = View.VISIBLE
        if (adapterSDK30!!.listImages != null && adapterSDK30!!.listImages.size > 0) {
            no_image?.let {
                it.visibility = View.GONE
            }
            no_image_txt?.let {
                it.visibility = View.GONE
            }
            go_to_app?.let {
                it.visibility = View.GONE
            }
        } else {
            no_image?.let {
                it.visibility = View.VISIBLE
            }
            no_image_txt?.let {
                it.visibility = View.VISIBLE
            }
            go_to_app?.let {
                it.visibility = View.VISIBLE
            }
        }
        adapterSDK30!!.notifyDataSetChanged()

        status_img_swipe.isRefreshing = false
    }


    override fun onDestroyView() {
        job.cancel()
        super.onDestroyView()
    }


    fun processStatusFetchFromChild(completed: () -> Unit) {
        val statusSelection = tinyDB.getInt(Constants.status_accounts)
        if (statusSelection == 0 || statusSelection == 1) {
            if (dataRepository.WhatsAppStatuses == null) {
                (activity as StatusActivity).setUpWAURIs()
            }
            var documentFileStatuses: DocumentFile? = dataRepository.WhatsAppStatuses
            if (statusSelection == 1) {
                documentFileStatuses = dataRepository.WhatsAppBusinessStatuses
            }
            if (documentFileStatuses != null) {
                launch(Dispatchers.Main) {
                    val statusImages1 = arrayListOf<DocumentFile>()

                    async(Dispatchers.IO) {
                        //this takes time ; want to fetch this same as WhatsApp Gallery
                        statusImages1.addAll(documentFileStatuses!!.listFiles().filter {
                            it.mimeType.equals(Constants.MIME_TYPE_IMG_PNG) || it.mimeType.equals(
                                Constants.MIME_TYPE_IMG_JPG
                            ) || it.mimeType.equals(Constants.MIME_TYPE_IMG_JPEG)
                        }.sortedByDescending { it.lastModified() })
                    }.await()
                    Timber.e("processStatusFetch:Done")
                    sharedViewModel.statusImages.postValue(statusImages1)
                    completed()
                }
            } else {
                Timber.e("processStatusFetch:Done")
                sharedViewModel.statusImages.postValue(arrayListOf<DocumentFile>())
                completed()
            }
        } else {
            Timber.e("processStatusFetch:Done")
            sharedViewModel.statusImages.postValue(arrayListOf<DocumentFile>())
            completed()


        }
    }

}

请注意我使用的WhatsApp文件夹路径是

val whatsapp_images_path11 = "/Android/media/“ +"com.whatsapp" +"/WhatsApp/Media/WhatsAppImages/"

在这种情况下,我如何使用MediaStore,这样我就不需要使用列表的排序和过滤功能了?获取java.io文件并不重要,只有我也可以使用URI。

共有1个答案

丘浩宕
2023-03-14

使用DocumentFile处理SAF资源确实很慢。

更好地使用Documents合约来做到这一点。

它的速度大约是DocumentFile的20倍,大约是经典文件类的20倍。

使用MediaStore隐藏文件夹应该是可能的。您无法使用mediastore创建隐藏文件夹。但是,如果您设法使它们不使用mediastore,您应该能够使用mediastore列出其中的文件。如果它们被扫描。如果它们属于您的应用程序

 类似资料:
  • 问题内容: 我需要将文件和文件夹隐藏在Windows和Linux上。我知道在后面加上“。” 文件或文件夹的开头将使其在Linux上隐藏。如何在Windows上隐藏文件或文件夹? 问题答案: 对于Java 6及更低版本, 您将需要使用本地调用,这是Windows的一种方法 您应该了解有关win32-api或Java Native的知识。

  • 问题内容: 我想使用Java应用程序创建一个隐藏文件夹。该程序应跨平台运行。那么如何编写一个可以创建隐藏文件夹的程序。 我尝试使用 它创建一个未隐藏的目录。 问题答案: 隐藏文件/文件夹的概念是特定于操作系统的,无法通过Java API访问。 在Linux中,许多程序默认情况下会隐藏名称以点开头的文件和文件夹-这样做很容易。 在Windows中,“隐藏”是存储在文件系统中的特殊标志。没有用于更改它

  • 本文向大家介绍如何使用Python删除隐藏的文件和文件夹?,包括了如何使用Python删除隐藏的文件和文件夹?的使用技巧和注意事项,需要的朋友参考一下 在Unix OS(OSX,Linux等)上,隐藏文件以“。”开头。因此我们可以使用简单的startwith check过滤掉它们。在Windows上,我们需要检查文件属性,然后确定文件/文件夹是否隐藏。 示例 例如,您可以使用以下代码删除所有隐藏文

  • 通过使用android 11(SDK API 30)设备下面的一些方法 但在Android11中运行良好。无法使用如下所示的方法使其im 函数调用 uri是我从拾取器的响应中获得的视频路径。 copyFile的函数调用 错误:new ;path:/storage/emulated/0/download/stop/mp4_20210128225711.mp4 system.err:java.io.f

  • 问题内容: 试图隐藏文件夹没有成功。我发现了: 但这对我没有用。我究竟做错了什么? 问题答案: 您的代码有两件事,都与文件夹名称文字有关。该函数需要Unicode字符串参数。您可以通过在字符串前面加上字符来指定其中之一。其次,字符串中的任何文字反斜杠字符都必须加倍,或者您也可以为其添加前缀。下面的代码中使用了一个双前缀。 您可以在此处找到Windows的系统错误代码。要在资源管理器中查看属性更改的

  • 问题内容: 我需要列出文件夹中包含目录路径的所有文件。我尝试使用,这显然是完美的解决方案。 但是,它还会列出隐藏的文件夹和文件。我希望我的应用程序不列出任何隐藏的文件夹或文件。有没有可以用来使其不产生任何隐藏文件的标志? 跨平台对我而言并不是很重要,如果只适用于linux(。*模式)就可以了 问题答案: 不,没有选择会跳过这些。您需要自己这样做(这很容易): 注意切片分配;递归遍历中列出的子目录。