Android代码扫描手机内图片、视频、音频、文档等文件

通过遍历手机文件的方式获取手机内存里全部的相关类型文件

一、前情提要

由于Android对于权限把控的严格控制,在Android不同版本下需要对扫描逻辑以及权限做不同的处理。
(扫描完成你可能会在你的手机里发现很多有趣的图片或者其他东西~~~)

1、Android11以下设备

如果当前设备在Android11以下则仅需要申请WRITE_EXTERNAL_STORAGE,READ_EXTERNAL_STORAGE读写权限即可遍历访问全部文件夹。

2、Android11以上设备

Android11以上设备如果想访问手机内的全部文件夹则需要获取《全部文件访问权限》,这个权限无法通过弹窗的方式点击允许,需要跳转系统设置页面手动点开开关。
在这里插入图片描述

而在Android11及以上设备中,获取到全部文件访问权限并不代表你可以访问到全部文件,其中重要的包含android/data文件夹是无法正常访问的,这个时候我们还需要去申请data文件夹的访问权限如下图,包括这个文件夹下的文件遍历也与其他文件夹不同,下面可自行看代码。
在这里插入图片描述

二、扫描流程及代码

1、权限申请

Manifest.xml静态申请

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
Android11以下申请普通读写权限
    /**
     *  申请普通读写权限
     */
    private fun permissionReadWrite() {
        //请求读写权限
        PermissionUtils.permission(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )
            .callback(object : PermissionUtils.SimpleCallback {
                override fun onGranted() {
                    //已经获得权限则直接进入扫描
                    startScan()
                }

                override fun onDenied() {
                    showError("Permission denied. Unable to use the feature.")
                }
            }).request()
    }
申请全部文件访问权限
	//检查是否拥有全部文件访问权限
    if (!Environment.isExternalStorageManager()) {
    	IdentSPUtil.setIsToGetPermission(true)
    	var intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
    	intent.data = Uri.parse("package:$packageName")
    	startActivityForResult(intent, 9970)
    }
                        
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
            //获取全部文件权限返回
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
            	//权限获取成功
            } else {
                showError("Permission denied. Unable to use the feature.")
            }
    }
申请Data文件夹访问权限
/**
 * 判断是否有android/data文件夹权限
 */
fun isGrantAndroidDataDirPermission(): Boolean {
    //如果当前小于android11就默认获取成功吧,因为android11以下不需要获取data文件夹权限就可以直接访问
    if ( Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
        return true
    }
    val uri =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){//Android13及以上的data文件夹uri有所变化,所以需要分开处理
        "content://com.android.externalstorage.documents/tree/primary%3AA%E2%80%8Bndroid%2Fdata"
//        "content://com.android.externalstorage.documents/tree/primary%3AA%E2%80%8Bndroid%2Fdata/document/primary%3AA%E2%80%8Bndroid%2Fdata"
    }else{
        "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata"
//        "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata/document/primary%3AAndroid%2Fdata"
    }
    val uriPermissionList = Utils.getApp().contentResolver.persistedUriPermissions
    for (persistedUriPermission in uriPermissionList) {
        if (persistedUriPermission.isReadPermission && persistedUriPermission.uri.toString() == uri) {
            return true
        }
    }
    return false
}


        if (!isGrantAndroidDataDirPermission()) {
                    //获取data目录访问权限
                    val uri =
                        Uri.parse(
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                                "content://com.android.externalstorage.documents/tree/primary%3AA%E2%80%8Bndroid%2Fdata"
                            } else{
                                "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata"
                            }
                        )
                    val documentFile =
                        DocumentFile.fromTreeUri(this@ScanModeChooseActivity, uri)
                    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
                    intent.flags = (Intent.FLAG_GRANT_READ_URI_PERMISSION
                            or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                            or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                            or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
                    documentFile?.let {
                        intent.putExtra(
                            DocumentsContract.EXTRA_INITIAL_URI,
                            documentFile.uri
                        )
                        startActivityForResult(intent, 9969)
                    } ?: let {
                        showError("Permission denied. Unable to use the feature.")
                    }
        }
        

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 9969) {//data文件夹权限获取结果返回
            var uri: Uri? = data?.data
            uri?.let {
                if (data?.flags != null) {//获取成功
                	//保存data权限的获取状态
                    contentResolver.takePersistableUriPermission(
                        it,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    )
                    //跳转扫描
                } else {//获取失败
                    showError("Permission denied. Unable to use the feature.")
                }
            } ?: let {//获取失败
                showError("Permission denied. Unable to use the feature.")
            }
        }
    }

2、扫描过程

这里建了一个扫描文件工具类,在权限获取成功后直接调用,内部分为普通扫描和深度扫描,普通扫描表示遍历能遍历到的全部文件夹,深度扫描表示除了普通扫描的遍历,还有data文件夹的遍历,而data文件夹的遍历过程比较费时,因为无法直接访问里面的文件,所以不能实施预览,这里的处理方案是如果扫描到了相应文件,则备份对应文件到本项目的内部存储空间中,再通过路径进行访问预览。在扫描到文件后通过handler进行保存
下面直接放完整代码

工具类

import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Message
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.documentfile.provider.DocumentFile
import com.blankj.utilcode.util.ConvertUtils
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.TimeUtils
import com.sugoilab.restore.bean.FileEntity
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale


/**
 * 通用文件类型
 */
const val TYPE_IMAGE_TEXT = "Photo"
const val TYPE_VIDEO_TEXT = "Video"
const val TYPE_AUDIO_TEXT = "Audio"
const val TYPE_DOC_TEXT = "Doc"
const val TYPE_PDF_TEXT = "Pdf"
const val TYPE_PPT_TEXT = "Ppt"
const val TYPE_XLS_TEXT = "Xls"
const val TYPE_TXT_TEXT = "Txt"
const val TYPE_ALL_WENBEN_TEXT = "Documents"
const val TYPE_COMPRESS_TEXT = "Compress"

/**
 * 扫描过程标识
 */
const val SCAN_STATUS_SCANNING = 1                              //扫描过程中
const val SCAN_STATUS_SCAN_PATH = 2                             //正在扫描的路径
const val SCAN_STATUS_COMPLETE = 3                              //扫描完成

const val SCAN_TYPE_NORMAL = 1
const val SCAN_TYPE_DEEP = 2
class ScanFileUtil(val handler: Handler, val context: Context) {

    private var thread: Thread? = null

    fun getFileLastModifiedTime(paramFile: File): String? {
        val calendar: Calendar = Calendar.getInstance()
        val l = paramFile.lastModified()
        val simpleDateFormat = SimpleDateFormat("dd/MM/yyyy")
        calendar.timeInMillis = l
        return simpleDateFormat.format(calendar.time)
    }

    fun isHarmonyOs(): Boolean {
        return try {
            val clazz = Class.forName("android.os.SystemProperties")
            "Harmony".equals(
                clazz.getMethod("getOsBrand", *arrayOfNulls<Class<*>>(0))
                    .invoke(clazz, arrayOfNulls<Any>(0)).toString(), ignoreCase = true
            )
        } finally {
            val exception: java.lang.Exception? = null
        }
    }

    private fun listFilesInDirWithFilterNew(
        paramFile: File?,
        type: String
    ) {
        if (paramFile == null || !FileUtils.isDir(paramFile)) return
//        try {
//            if (isHarmonyOs() && paramFile.absolutePath.contains("Android/data")) return ArrayList()
//        } catch (exception: Exception) {
//            exception.printStackTrace()
//        }
        val arrayOfFile = paramFile.listFiles()
        if (arrayOfFile != null && arrayOfFile.isNotEmpty()) {
            val j = arrayOfFile.size
            for (i in 0 until j) {
                if (Thread.currentThread().isInterrupted) {
                    LogUtils.e("xxx", "线程被中断")
                    throw InterruptedException()
                }
                val file = arrayOfFile[i]
                //返回当前正在扫描的文件路径
                val message1 = Message.obtain()
                message1.what = SCAN_STATUS_SCAN_PATH
                val bundle1 = Bundle()
                val fileEntity1 = FileEntity(UI_TYPE_MAIN)
                fileEntity1.filePath = file.absolutePath
                bundle1.putSerializable("obj", fileEntity1)
                message1.data = bundle1
                this.handler.sendMessage(message1)

                if (file.isDirectory) {
                    listFilesInDirWithFilterNew(file, type)
                } else {
                    if (file.absolutePath.contains("com.sugoilab.restore") || file.absolutePath.contains(
                            "SugoilabFileRecovery"
                        )
                    ) {
                        continue
                    }
//                    if (MyApplication.scanFileList.size > 100)
//                        return
                    //判断并获取当前扫描的文件是否是对应类型文件
                    val fileEntity: FileEntity = findSingleFileUpdateUi(file, type) ?: continue
                    //文件获取成功提醒线程更新
                    val message = Message.obtain()
                    message.what = SCAN_STATUS_SCANNING
                    val bundle = Bundle()
                    bundle.putSerializable("obj", fileEntity)
                    message.data = bundle
                    this.handler.sendMessage(message)
                }
            }
        }
    }

    private fun findSingleFileUpdateUi(paramFile: File, type: String): FileEntity? {
        try {
            val fileEntity = FileEntity(UI_TYPE_MAIN)
            val stringBuilder1 = StringBuilder()
            stringBuilder1.append("file=1111==>")
            stringBuilder1.append(paramFile.absolutePath)
            LogUtils.e("xxx", stringBuilder1.toString())
            val filePath = paramFile.absolutePath
            val filePathLowCase = filePath.lowercase(Locale.getDefault())
            val fileType: String = CheckFileTypeUtil.getFileType(filePath)
            when (type) {
                TYPE_IMAGE_TEXT -> {
                    if (filePathLowCase.endsWith(".bmp") || filePathLowCase.endsWith(".jpg") || filePathLowCase.endsWith(
                            ".png"
                        ) || filePathLowCase.endsWith(
                            ".tif"
                        ) || filePathLowCase.endsWith(".gif") || filePathLowCase.endsWith(".webp") || fileType.equals(
                            "bmp",
                            ignoreCase = true
                        ) || fileType.equals("jpg", ignoreCase = true) || fileType.equals(
                            "png",
                            ignoreCase = true
                        ) || fileType.equals("tif", ignoreCase = true) || fileType.equals(
                            "gif",
                            ignoreCase = true
                        ) || fileType.equals("webp", ignoreCase = true)
                    ) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_VIDEO_TEXT -> {
                    if (filePathLowCase.endsWith(".mp4") || filePathLowCase.endsWith(".avi") || filePathLowCase.endsWith(
                            ".3gp"
                        ) || filePathLowCase.endsWith(
                            ".wmv"
                        ) || filePathLowCase.endsWith(".flv") || filePathLowCase.endsWith(".rmvb") || filePathLowCase.endsWith(
                            ".mov"
                        ) || filePathLowCase.contains(
                            ".file_recycle"
                        ) && (fileType.equals("flv", ignoreCase = true) || fileType.equals(
                            "wmv",
                            ignoreCase = true
                        ) || fileType.equals("3gp", ignoreCase = true) || fileType.equals(
                            "avi",
                            ignoreCase = true
                        ) || fileType.equals("mp4", ignoreCase = true) || fileType.equals(
                            "rmvb",
                            ignoreCase = true
                        ) || fileType.equals(
                            "mov",
                            ignoreCase = true
                        )) || filePathLowCase.contains("recycle") && (fileType.equals(
                            "flv",
                            ignoreCase = true
                        ) || fileType.equals("wmv", ignoreCase = true) || fileType.equals(
                            "3gp",
                            ignoreCase = true
                        ) || fileType.equals("avi", ignoreCase = true) || fileType.equals(
                            "mp4",
                            ignoreCase = true
                        )) || fileType.equals("rmvb", ignoreCase = true) || fileType.equals(
                            "mov",
                            ignoreCase = true
                        )
                    ) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_AUDIO_TEXT -> {
                    if (filePathLowCase.endsWith(".mp3") || filePathLowCase.endsWith(".amr") || filePathLowCase.endsWith(
                            ".m4a"
                        ) || filePathLowCase.endsWith(
                            ".wav"
                        ) || filePathLowCase.endsWith(".aac") || filePathLowCase.endsWith(".ogg") || fileType.equals(
                            "mp3",
                            ignoreCase = true
                        ) || fileType.equals("amr", ignoreCase = true) || fileType.equals(
                            "m4a",
                            ignoreCase = true
                        ) || fileType.equals("wav", ignoreCase = true) || fileType.equals(
                            "aac",
                            ignoreCase = true
                        ) || fileType.equals("ogg", ignoreCase = true)
                    ) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_DOC_TEXT -> {
                    if (filePathLowCase.endsWith(".doc") || filePathLowCase.endsWith(".docx")) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_PDF_TEXT -> {
                    if (filePathLowCase.endsWith(".pdf")) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_XLS_TEXT -> {
                    if (filePathLowCase.endsWith(".xls") || filePathLowCase.endsWith(".xlsx")) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_PPT_TEXT -> {
                    if (filePathLowCase.endsWith(".ppt") || filePathLowCase.endsWith(".pptx")) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_TXT_TEXT -> {
                    if (filePathLowCase.endsWith(".txt")) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_ALL_WENBEN_TEXT -> {
                    if (filePathLowCase.endsWith(".doc") || filePathLowCase.endsWith(".docx") || filePathLowCase.endsWith(
                            ".pdf"
                        ) || filePathLowCase.endsWith(
                            ".xls"
                        ) || filePathLowCase.endsWith(".xlsx") || filePathLowCase.endsWith(".ppt") || filePathLowCase.endsWith(
                            ".pptx"
                        )
                    ) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }

                TYPE_COMPRESS_TEXT -> {
                    if (filePathLowCase.endsWith(".zip") || filePathLowCase.endsWith(".rar") || filePathLowCase.endsWith(
                            ".7z"
                        )
                    ) {
                        val fileName = paramFile.name
                        val fileTime = getFileLastModifiedTime(paramFile)
                        val fileTimeMils = paramFile.lastModified()
                        val fileSizeStr = FileUtils.getSize(paramFile)
                        val fileLength = FileUtils.getLength(paramFile)
                        fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                        fileEntity.filePath = filePath
                        fileEntity.fileName = fileName
                        fileEntity.fileType = type
                        fileEntity.fileRealType = fileType
                        fileEntity.fileLastModifiedTime = fileTime
                        fileEntity.fileLastModifiedTimeMils = fileTimeMils
                        fileEntity.fileSizeStr = fileSizeStr
                        fileEntity.fileSizeLong = fileLength
                        return fileEntity
                    }
                }
            }
            return null

        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }

    fun getFiles(dirfiles: List<File?>, type: String) {
        thread = Thread {
            try {
                for (file in dirfiles) {
                    listFilesInDirWithFilterNew(
                        file,
                        type
                    )
                }
                val message = Message.obtain()
                message.what = SCAN_STATUS_COMPLETE
                handler.sendMessage(message)
            } catch (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
        thread?.start()
    }

    /**
     * 从全部的手机文件进行搜索——普通扫描,不扫描data文件夹
     */
    fun getAllFilesNormal(type: String) {
        thread = Thread {
            try {
                //扫描外部文件信息
                listFilesInDirWithFilterNew(Environment.getExternalStorageDirectory(), type)

                val message = Message.obtain()
                message.what = SCAN_STATUS_COMPLETE
                handler.sendMessage(message)
            } catch (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
        thread?.start()
    }

    /**
     * 从全部的手机文件进行搜索——深度扫描
     */
    fun getAllFilesDeep(type: String) {
        thread = Thread {
            try {
                //先扫描外部文件信息
                listFilesInDirWithFilterNew(Environment.getExternalStorageDirectory(), type)
                //再扫描Android data文件夹信息,扫描方式不同
                getAndroidDataFile(type)
                val message = Message.obtain()
                message.what = SCAN_STATUS_COMPLETE
                handler.sendMessage(message)
            } catch (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
        thread?.start()
    }

    /**
     * 扫描Android/data文件夹下信息
     */
    private fun getAndroidDataFile(type: String) {
        val dirUri =
            Uri.parse(
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    "content://com.android.externalstorage.documents/tree/primary%3AA%E2%80%8Bndroid%2Fdata"
                } else{
                    "content://com.android.externalstorage.documents/tree/primary%3AAndroid%2Fdata"
                }
            )
        val documentFile = DocumentFile.fromTreeUri(context, dirUri)
        getDataFileFromDir(documentFile, type)
    }

    private fun getDataFileFromDir(
        documentFile: DocumentFile?,
        type: String
    ) {
        //遍历DocumentFile,但是要避开本app的缓存目录,不然会出现很多重复图片
        val files = documentFile?.listFiles()
        files?.let {
            for (file in files) {
                if (Thread.currentThread().isInterrupted) {
                    LogUtils.e("xxx", "线程被中断")
                    throw InterruptedException()
                }
                if (file.uri.path?.contains("com.sugoilab.restore") == true) {
                    continue
                }
//                if (MyApplication.scanFileList.size > 100)
//                    return
                //返回当前正在扫描的文件路径
                val message1 = Message.obtain()
                message1.what = SCAN_STATUS_SCAN_PATH
                val bundle1 = Bundle()
                val fileEntity1 = FileEntity(UI_TYPE_MAIN)
                fileEntity1.filePath = file.uri.path
                bundle1.putSerializable("obj", fileEntity1)
                message1.data = bundle1
                this.handler.sendMessage(message1)

                if (file.isDirectory)//如果是目录则重复本方法往目录下执行。
                    getDataFileFromDir(file, type)
                else {//如果是文件则判断文件类型
                    try {
                        val fileEntity = getSingleFataFile(file, type) ?: continue
                        //文件获取成功提醒线程更新
                        val message = Message.obtain()
                        message.what = SCAN_STATUS_SCANNING
                        val bundle = Bundle()
                        bundle.putSerializable("obj", fileEntity)
                        message.data = bundle
                        this.handler.sendMessage(message)
                    } catch (e: Exception) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private fun getSingleFataFile(
        file: DocumentFile,
        type: String
    ): FileEntity? {
        file.type?.let { fileType ->
            LogUtils.e("xxx", "data文件夹文件:${file.uri.path}")
            when (type) {
                TYPE_IMAGE_TEXT -> {
                    if (fileType.contains("image")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        //表示是图片文件,则做保存
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType = CheckFileTypeUtil.getFileType(fileEntity.filePath)
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_VIDEO_TEXT -> {
                    if (fileType.contains("video")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType = CheckFileTypeUtil.getFileType(fileEntity.filePath)
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_AUDIO_TEXT -> {
                    if (fileType.contains("audio")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType = CheckFileTypeUtil.getFileType(fileEntity.filePath)
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_DOC_TEXT -> {
                    if (fileType.equals("application/msword") || fileType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        if (fileType.equals("application/msword"))
                            fileEntity.fileRealType = "doc"
                        else
                            fileEntity.fileRealType = "docx"
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_PDF_TEXT -> {
                    if (fileType.equals("application/pdf")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        if (fileType.equals("application/pdf"))
                            fileEntity.fileRealType = "pdf"
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_XLS_TEXT -> {
                    if (fileType.equals("application/vnd.ms-excel") || fileType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        if (fileType.equals("application/vnd.ms-excel"))
                            fileEntity.fileRealType = "xls"
                        else
                            fileEntity.fileRealType = "xlsx"
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_PPT_TEXT -> {
                    if (fileType.equals("application/vnd.ms-powerpoint") || fileType.equals("application/vnd.openxmlformats-officedocument.presentationml.presentation")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType = CheckFileTypeUtil.getFileType(fileEntity.filePath)
                        if (fileType.equals("application/vnd.ms-powerpoint"))
                            fileEntity.fileRealType = "ppt"
                        else
                            fileEntity.fileRealType = "pptx"
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_TXT_TEXT -> {
                    if (fileType.equals("text/plain")) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType = "txt"
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_ALL_WENBEN_TEXT -> {
                    if (fileType.equals("application/msword") || fileType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document") || fileType.equals(
                            "application/pdf"
                        ) || fileType.equals("application/vnd.ms-excel") || fileType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") || fileType.equals(
                            "application/vnd.ms-powerpoint"
                        ) || fileType.equals("application/vnd.openxmlformats-officedocument.presentationml.presentation")
                    ) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType =
                            when (fileType) {
                                "application/msword" -> {
                                    "doc"
                                }

                                "application/vnd.openxmlformats-officedocument.wordprocessingml.document" -> {
                                    "docx"
                                }

                                "application/pdf" -> {
                                    "pdf"
                                }

                                "application/vnd.ms-excel" -> {
                                    "xls"
                                }

                                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -> {
                                    "xlsx"
                                }

                                "application/vnd.ms-powerpoint" -> {
                                    ".ppt"
                                }

                                "application/vnd.openxmlformats-officedocument.presentationml.presentation" -> {
                                    ".pptx"
                                }

                                else -> {
                                    ""
                                }
                            }
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                TYPE_COMPRESS_TEXT -> {
                    if (fileType.endsWith(".zip") || fileType.endsWith(".rar") || fileType.endsWith(
                            ".7z"
                        )
                    ) {
                        val fileEntity = FileEntity(UI_TYPE_MAIN)
                        fileEntity.fileName = file.name
                        fileEntity.fileType = type
                        fileEntity.fileLastModifiedTime = TimeUtils.millis2String(
                            file.lastModified(),
                            SimpleDateFormat("dd/MM/yyyy")
                        )
                        fileEntity.fileLastModifiedTimeMils = file.lastModified()
                        fileEntity.fileSizeStr = ConvertUtils.byte2FitMemorySize(file.length())
                        fileEntity.fileSizeLong = file.length()
                        fileEntity.filePath = backUpFileFromDocumentFile(file)
                        fileEntity.fileRealType = CheckFileTypeUtil.getFileType(fileEntity.filePath)
                        file.type
                        fileEntity.isDataFile = true
                        fileEntity.dirTypeStr = "data"
                        return fileEntity
                    }
                }

                else -> {

                }
            }
        }
        return null
    }

    /**
     * 备份data文件夹下的文件到项目内部存储
     */
    private fun backUpFileFromDocumentFile(documentFile: DocumentFile): String {
        try {
            var fileName = ""//原文件名
            var fileLast = ""//原文件后缀
            documentFile.name?.let {
                fileName = it.substring(0, it.lastIndexOf("."))
                fileLast = it.substring(it.lastIndexOf("."), it.lastIndex + 1)
            }
            //新的备份文件名的创建规则是     原文件名_原文件大小_原文件创建时间
            var newFileName =
                "${fileName}_${documentFile.length()}_${documentFile.lastModified()}$fileLast"

            //新文件的路径
            val newFilePath =
                context.getExternalFilesDir("scanBackUp")?.absolutePath + "/$newFileName"
            LogUtils.e(
                "xxx",
                "documentFileToFile fileName=${documentFile.name} fileUri=${documentFile.uri}",
                "newFile fileName=$newFileName filePath=$newFilePath"
            )
            //先判断这个文件是否已经查找备份过,如果已经存在则不在创建,直接返回就可。
            if (FileUtils.isFileExists(newFilePath)) {
                return newFilePath
            }
            //DocumentFile输入流
            val newFile =
                File(newFilePath)
            val inputStream = context.contentResolver.openInputStream(documentFile.uri)
            val outputStream = FileOutputStream(newFile)
            val buf = ByteArray(1024)
            var len: Int
            inputStream?.let {
                while (it.read(buf).also { it1 -> len = it1 } > 0) {
                    outputStream.write(buf, 0, len)
                }
            } ?: let {
                outputStream.close()
                return ""
            }
            inputStream.close()
            outputStream.close()
            return newFilePath
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
        return ""
    }

    fun destory() {
        thread?.interrupt()
        thread = null
    }

    @RequiresApi(Build.VERSION_CODES.R)
    private fun getFileByMediaApi(type: String) {
        var selection = ""
        when (type) {
            TYPE_IMAGE_TEXT -> {
                selection =
                    MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
            }

            TYPE_VIDEO_TEXT -> {
                selection =
                    MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
            }

            TYPE_AUDIO_TEXT -> {
                selection =
                    MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO
            }

            TYPE_ALL_WENBEN_TEXT -> {
                selection =
                    MediaStore.Files.FileColumns.MEDIA_TYPE + " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_DOCUMENT
            }

            else -> {
                return
            }
        }
        context.contentResolver.query(
            MediaStore.Files.getContentUri("external"),
            null,
            selection,
            null,
            null
        )?.let {
            while (it.moveToNext()) {
                //返回当前正在扫描的文件路径
                val message1 = Message.obtain()
                message1.what = SCAN_STATUS_SCAN_PATH
                val bundle1 = Bundle()
                val fileEntity1 = FileEntity(UI_TYPE_MAIN)
                val filePath =
                    it.getString(it.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA))
                fileEntity1.filePath = filePath
                bundle1.putSerializable("obj", fileEntity1)
                message1.data = bundle1
                this.handler.sendMessage(message1)

                val fileEntity = FileEntity(UI_TYPE_MAIN)
                val fileName =
                    it.getString(it.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME))
                val fileTimeMils =
                    it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_MODIFIED)) * 1000
                val fileTime = TimeUtils.millis2String(fileTimeMils, "dd/MM/yyyy")
                val fileLength =
                    it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.SIZE))
                val fileSizeStr = FileUtils.getSize(filePath)
                fileEntity.dirTypeStr = getDirTypeStrByPath(filePath)
                fileEntity.filePath = filePath
                fileEntity.fileName = fileName
                fileEntity.fileType = type
                fileEntity.fileRealType =
                    if (type == TYPE_ALL_WENBEN_TEXT) {
                        when (it.getString(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))) {
                            "application/msword" -> {
                                "doc"
                            }

                            "application/vnd.openxmlformats-officedocument.wordprocessingml.document" -> {
                                "docx"
                            }

                            "application/pdf" -> {
                                "pdf"
                            }

                            "application/vnd.ms-excel" -> {
                                "xls"
                            }

                            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -> {
                                "xlsx"
                            }

                            "application/vnd.ms-powerpoint" -> {
                                ".ppt"
                            }

                            "application/vnd.openxmlformats-officedocument.presentationml.presentation" -> {
                                ".pptx"
                            }

                            else -> {
                                ""
                            }
                        }
                    } else
                        CheckFileTypeUtil.getFileType(filePath)
                fileEntity.fileLastModifiedTime = fileTime
                fileEntity.fileLastModifiedTimeMils = fileTimeMils
                fileEntity.fileSizeStr = fileSizeStr
                fileEntity.fileSizeLong = fileLength
                //文件获取成功提醒线程更新
                val message = Message.obtain()
                message.what = SCAN_STATUS_SCANNING
                val bundle = Bundle()
                bundle.putSerializable("obj", fileEntity)
                message.data = bundle
                this.handler.sendMessage(message)
            }
        }
    }

    private fun getDirTypeStrByPath(path: String): String {
        return if (!path.contains("/"))
            "other"
        else {
            val data =
                path.split("/")
            val str = data[data.size - 2]
            str
        }
    }

}

文件实体类


import android.graphics.Bitmap
import com.chad.library.adapter.base.entity.MultiItemEntity
import java.io.File
import java.io.Serializable

//itemType只是我为了对文件分类而做的标识,不需要可去掉
class FileEntity(override val itemType: Int) : Serializable, MultiItemEntity {
    var fileLastModifiedTime: String? = null//文件上次修改时间的转换文案
    var fileLastModifiedTimeMils: Long? = null//文件上次修改时间的毫秒
    var fileName: String? = null//文件名
    var filePath: String? = null//文件路径
    var fileRealType: String? = null//文件真实类型
    var fileSizeStr: String? = null//文件大小的转换文案
    var fileSizeLong: Long? = null//文件大小的字节长度
    var fileType: String? = null//文件类型-Utils的参数
    var isDataFile = false//是否是android/data中的文件
    var isCheck = false//是否被选中
    var bitmap: Bitmap? = null//当前图片的bitmap文件
    var recoverFilePath: String? = null
    var dirTypeStr = ""
}

扫描启动


    private lateinit var scanHandler: Handler
    private lateinit var scanUtil: ScanFileUtil
    
    private var scanFileList = mutableListOf<FileEntity>()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        scanHandler = @SuppressLint("HandlerLeak")
        object : Handler() {
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                when (msg.what) {
                    SCAN_STATUS_SCANNING -> {
                        //扫描中
                        val fileEntity = msg.data.getSerializable("obj") as FileEntity
                        if (TextUtils.isEmpty(fileEntity.filePath))
                            return
                        val file = File(fileEntity.filePath)
                        try {
                            if (getFileSize(file) > 0) {
                                scanFileList.add(fileEntity)
                                binding.tvScanning.text =
                                    "Scanning(${scanFileList.size})..."

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

                    SCAN_STATUS_SCAN_PATH -> {
                        //正在扫描的路径
                        val fileEntity = msg.data.getSerializable("obj") as FileEntity
                        binding.tvPath.text = fileEntity.filePath
                    }

                    SCAN_STATUS_COMPLETE -> {
                        //扫描完成,跳转
                        LogUtils.e("扫描完成", scanFileList.size)
                    }
                }
            }
        }
        
        scanFile()
    }

    private fun scanFile() {
        if (mode == SCAN_TYPE_DEEP)
            scanUtil.getAllFilesDeep(type)
        else
            scanUtil.getAllFilesNormal(type)
    }

    override fun onDestroy() {
        super.onDestroy()
        scanUtil.destory()
        scanHandler.removeCallbacksAndMessages(null)
    }

    fun getFileSize(paramFile: File): Long {
        val bool = paramFile.exists()
        var size = 0L
        if (bool) {
            try {
                val fileInputStream = FileInputStream(paramFile)
                size = fileInputStream.available().toLong()
                fileInputStream.close()
                return size
            } catch (iOException: IOException) {
                iOException.printStackTrace()
            }
        }
        return size
    }
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值