Android 10

如果項目完全沒有適配Android10 可以使用該方法通用Android10 沙盒機(jī)制

requestLegacyExternalStorage=ture

 android:maxSdkVersion="28"  //最大 

分區(qū)存儲

https://blog.csdn.net/mr_lichao/article/details/107516514
存儲權(quán)限

Android Q 在外部存儲設(shè)備中為每個應(yīng)用提供了一個“隔離存儲沙盒”(例如 /sdcard)。任何其他應(yīng)用都無法直接訪問您應(yīng)用的沙盒文件。由于文件是您應(yīng)用的私有文件,因此您不再需要任何權(quán)限即可在外部存儲設(shè)備中訪問和保存自己的文件。此變更可讓您更輕松地保證用戶文件的隱私性,并有助于減少應(yīng)用所需的權(quán)限數(shù)量。

沙盒,簡單而言就是應(yīng)用專屬文件夾,并且訪問這個文件夾無需權(quán)限。谷歌官方推薦應(yīng)用在沙盒內(nèi)存儲文件的地址為Context.getExternalFilesDir()下的文件夾。
比如要存儲一張圖片,則應(yīng)放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。

以下將按訪問的目標(biāo)文件的地址介紹如何適配。

  1. 訪問自己文件:Q中用更精細(xì)的媒體特定權(quán)限替換并取消了READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE權(quán)限,并且無需特定權(quán)限,應(yīng)用即可訪問自己沙盒中的文件。

  2. 訪問系統(tǒng)媒體文件:Q中引入了一個新定義媒體文件的共享集合,如果要訪問沙盒外的媒體共享文件,比如照片,音樂,視頻等,需要申請新的媒體權(quán)限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申請方法同原來的存儲權(quán)限。

  3. 訪問系統(tǒng)下載文件:對于系統(tǒng)下載文件夾的訪問,暫時沒做限制,但是,要訪問其中其他應(yīng)用的文件,必須允許用戶使用系統(tǒng)的文件選擇器應(yīng)用來選擇文件。

  4. 訪問其他應(yīng)用沙盒文件:如果你的應(yīng)用需要使用其他應(yīng)用在沙盒內(nèi)創(chuàng)建的文件,則需要點(diǎn)擊使用其他應(yīng)用的文件,。

所以請判斷當(dāng)應(yīng)用運(yùn)行在Q平臺上時,取消對READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE兩個權(quán)限的申請。并替換為新的媒體特定權(quán)限。

遇到的問題
1.Android10 申請不到 WRITE_EXTERNAL_STORAGE 失敗
2.在得到圖片,視頻 路徑拿不到SD卡視頻、圖片 結(jié)果沒有權(quán)限

解決辦法
1.將系統(tǒng)返回Uri 文件復(fù)制到自己項目內(nèi)這樣就不需要權(quán)限了

    var originPath: String? = ""
//這里需要做版本判斷如果Android10 以下返回的真實(shí)的path路徑
            lifecycleScope.launch(Dispatchers.Main) {//需要聲明線程
                if (entity.uri.toString().contains("content://") ) {
                    originPath = entity.uri?.let {
                        FileUtils.copyFile(
                                uri = it,
                                context = ChatJoyApplication.context!!,
                                endFeilName = ".jpg"
                        )
                    }
                } else {
                    originPath = entity.uri.toString()
                }
}
object FileUtils {
    /**
     * 立即刪除文件
     * @param path 路徑
     */
    fun deleteFilesAtOnce(path: String?) {
        try {
            val file = File(path)
            if (file.isDirectory) {
                val files = file.listFiles()
                for (value in files) {
                    deleteFilesAtOnce(value.absolutePath)
                }
            }
            file.delete()
        } catch (e: Exception) {
            PPLog.d("FileUtils deleteFilesAtOnce fail message : " + e.cause)
        }
    }

    /**
     * 刪除目錄下的所有文件
     */
    fun deleteFiles(path: String?) {
        val file = File(path)
        if (file.isDirectory) {
            val files = file.listFiles()
            for (value in files) {
                deleteFiles(value.absolutePath)
            }
        }
        file.deleteOnExit()
    }

    /**
     * 刪除文件
     */
    fun deleteFile(path: String?) {
        if (TextUtils.isEmpty(path)) return
        val file = File(path)
        if (!file.exists()) return
        file.deleteOnExit()
    }

    @Throws(IOException::class)
    fun addStringToFile(filePathAndName: String?, fileContent: String?) {
        val file = File(filePathAndName)
        if (!file.parentFile.exists()) {
            file.parentFile.mkdirs()
        }
        val myFilePath = File(filePathAndName)
        if (!myFilePath.exists()) {
            myFilePath.createNewFile()
        }
        val resultFile = FileWriter(myFilePath, true)
        val myFile = PrintWriter(resultFile)
        myFile.println(fileContent)
        myFile.close()
        resultFile.close()
    }

    @Throws(IOException::class)
    fun zip(src: String?, dest: String?) {
        //定義壓縮輸出流
        var out: ZipOutputStream? = null
        try {
            //傳入源文件
            val outFile = File(dest)
            val fileOrDirectory = File(src)
            //傳入壓縮輸出流
            out = ZipOutputStream(FileOutputStream(outFile))
            //判斷是否是一個文件或目錄
            //如果是文件則壓縮
            if (fileOrDirectory.isFile) {
                zipFileOrDirectory(out, fileOrDirectory, "")
            } else {
                //否則列出目錄中的所有文件遞歸進(jìn)行壓縮
                val entries = fileOrDirectory.listFiles()
                for (i in entries.indices) {
                    zipFileOrDirectory(out, entries[i], "")
                }
            }
        } catch (ex: IOException) {
            ex.printStackTrace()
        } finally {
            if (out != null) {
                try {
                    out.close()
                } catch (ex: IOException) {
                    ex.printStackTrace()
                }
            }
        }
    }

    @Throws(IOException::class)
    private fun zipFileOrDirectory(out: ZipOutputStream, fileOrDirectory: File, curPath: String) {
        var `in`: FileInputStream? = null
        try {
            //判斷目錄是否為null
            if (!fileOrDirectory.isDirectory) {
                val buffer = ByteArray(4096)
                var bytes_read: Int
                `in` = FileInputStream(fileOrDirectory)
                //歸檔壓縮目錄
                val entry = ZipEntry(curPath + fileOrDirectory.name)
                //將壓縮目錄寫到輸出流中
                out.putNextEntry(entry)
                while (`in`.read(buffer).also { bytes_read = it } != -1) {
                    out.write(buffer, 0, bytes_read)
                }
                out.closeEntry()
            } else {
                //列出目錄中的所有文件
                val entries = fileOrDirectory.listFiles()
                for (i in entries.indices) {
                    //遞歸壓縮
                    zipFileOrDirectory(out, entries[i], curPath + fileOrDirectory.name + "/")
                }
            }
        } catch (ex: IOException) {
            ex.printStackTrace()
        } finally {
            if (`in` != null) {
                try {
                    `in`.close()
                } catch (ex: IOException) {
                    ex.printStackTrace()
                }
            }
        }
    }

    fun deleteAllFiles(root: File) {
        try {
            val files = root.listFiles()
            if (files != null) for (f in files) {
                if (f.isDirectory) { // 判斷是否為文件夾
                    deleteAllFiles(f)
                    try {
                        f.delete()
                    } catch (e: Exception) {
                        PPLog.e(e.toString())
                    }
                } else {
                    if (f.exists()) { // 判斷是否存在
                        //deleteAllFiles(f);
                        try {
                            f.delete()
                        } catch (e: Exception) {
                            PPLog.e(e.toString())
                        }
                    }
                }
            }
        } catch (e: Exception) {
            PPLog.e(e.toString())
        }
    }

    /**
     * 獲取文件夾下面的文件大小
     */
    fun getFolderSize(file: File): Long {
        var size: Long = 0
        try {
            val fileList = file.listFiles()
            for (i in fileList.indices) {
                // 如果下面還有文件
                size = if (fileList[i].isDirectory) {
                    size + getFolderSize(fileList[i])
                } else {
                    size + fileList[i].length()
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return size
    }

    @Throws(IOException::class)
    fun copyFile(sourceFile: File?, destFile: File) {
        if (!destFile.exists()) {
            destFile.createNewFile()
        }
        var source: FileChannel? = null
        var destination: FileChannel? = null
        try {
            source = FileInputStream(sourceFile).channel
            destination = FileOutputStream(destFile).channel
            destination.transferFrom(source, 0, source.size())
        } finally {
            source?.close()
            destination?.close()
        }
    }

    /**
     * 格式化單位
     */
    fun getFormatSize(size: Double): String {
        val kiloByte = size / 1024
        if (kiloByte < 1) {
            return "0K"
        }
        val megaByte = kiloByte / 1024
        if (megaByte < 1) {
            val result1 = BigDecimal(java.lang.Double.toString(kiloByte))
            return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "KB"
        }
        val gigaByte = megaByte / 1024
        if (gigaByte < 1) {
            val result2 = BigDecimal(java.lang.Double.toString(megaByte))
            return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "MB"
        }
        val teraBytes = gigaByte / 1024
        if (teraBytes < 1) {
            val result3 = BigDecimal(java.lang.Double.toString(gigaByte))
            return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "GB"
        }
        val result4 = BigDecimal(teraBytes)
        return (result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
                + "TB")
    }
    // public static final String dir= Environment.getExternalStorageDirectory()+"/OkHttpDemo";
    /**
     * 保存本地
     */
    @Throws(IOException::class)
    fun saveFile2Local(response: Response, dir: String, fileName: String?): File {
        var inputStream: InputStream? = null
        var output: OutputStream? = null
        val file: File
        val temp = File(dir + File.separator + "temp")
        if (!temp.exists()) {
            temp.mkdir()
        }
        //        response.networkResponse().request().url();
//        String ext = filename.substring(filename.lastIndexOf(".") + 1).toUpperCase();
        return try {
            inputStream = response.body()!!.byteStream()
            file = File(dir + File.separator + "temp", fileName)
            output = FileOutputStream(file)
            val buff = ByteArray(1024 * 4)
            while (true) {
                val readed = inputStream.read(buff)
                if (readed == -1) {
                    break
                }
                //write buff
                output.write(buff, 0, readed)
            }
            output.flush()
            file.renameTo(File(dir, fileName))
            //            temp.deleteOnExit();
            file
        } catch (e: IOException) {
            e.printStackTrace()
            throw e
        } finally {
            try {
                inputStream?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            try {
                output?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }

        //return null;
    }

    fun getAppDownloadDir(context: Context): String {
        return context.filesDir.path
    }

    @Throws(IOException::class)
    fun saveCrashInfo2File(filePathAndName: String?, ex: Throwable?) {
        val sb = StringBuffer(returnNowTime())
        val writer: Writer = StringWriter()
        val pw = PrintWriter(writer)
        ex?.printStackTrace(pw)
        var cause = ex?.cause
        // 循環(huán)著把所有的異常信息寫入writer中
        while (cause != null) {
            cause.printStackTrace(pw)
            cause = cause.cause
        }
        pw.close() // 記得關(guān)閉
        val result = writer.toString()
        sb.append(result)
        // 保存文件
        addStringToFile(filePathAndName, sb.toString())
        PPLog.e("CaughtException", sb.toString())
    }

    fun returnNowTime(): String {
        val date = Date()
        var timeString = ""
        try {
            timeString = DateFormat.format("yyyyMMddkkmmss", date).toString()
        } catch (e: Exception) {
            PPLog.d("returnNowTime excepiton:" + e.message)
        }
        return timeString
    }

    private fun getTempVideoPath(): String =
            Configs.PENGPENG_CACHE + "video" + File.separator

    //臨時保存文件
    suspend fun copyFile(audioDst: String = getTempVideoPath(), uri: Uri, context: Context, endFeilName:String=".mp4"): String = withContext(Dispatchers.IO) {
        var path = ""
        val input: InputStream? = null
        val out: OutputStream? = null
        try {
            deleteFile(audioDst)
            //創(chuàng)建臨時文件夾
            val file = File(audioDst)
            file.mkdirs()
            val targetFile = File(audioDst + File.separator + System.currentTimeMillis() + endFeilName )
            if (!targetFile.exists()) {
                targetFile.createNewFile()
            }
            val audioAsset = context.contentResolver.openAssetFileDescriptor(uri, "r")
            val input = audioAsset?.createInputStream()
            val out = FileOutputStream(targetFile)
            val buffer = ByteArray(1024)
            var len: Int = 0
            while (-1 != input?.read(buffer).also { len = (it ?: -1) }) {
                out.write(buffer, 0, len)
            }
            out.flush()
            path = targetFile.absolutePath
        } catch (e:FileNotFoundException){
            Logger.d("%s %s ", "FileNotFoundException", "${e.message}")
            e.printStackTrace()
        } catch (e:IOException){
            Logger.d("%s %s ", "IOException", "${e.message}")
            e.printStackTrace()
        } catch (e: Exception) {
            Logger.d("%s %s ", "Exception", "${e.message}")
            e.printStackTrace()
        } finally {
            out?.close()
            input?.close()
        }
        path
    }

    //判斷文件是否存在
    fun fileIsExists(filename: String, context: Context): Boolean {
        try {
            val AbsolutePath = context.filesDir.absolutePath
            val f = File("$AbsolutePath/$filename")
            if (!f.exists()) {
                return false
            }
        } catch (e: Exception) {
            return false
        }
        return true
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容