如果項目完全沒有適配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)文件的地址介紹如何適配。
訪問自己文件:Q中用更精細(xì)的媒體特定權(quán)限替換并取消了
READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權(quán)限,并且無需特定權(quán)限,應(yīng)用即可訪問自己沙盒中的文件。訪問系統(tǒng)媒體文件:Q中引入了一個新定義媒體文件的共享集合,如果要訪問
沙盒外的媒體共享文件,比如照片,音樂,視頻等,需要申請新的媒體權(quán)限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申請方法同原來的存儲權(quán)限。訪問系統(tǒng)下載文件:對于系統(tǒng)下載文件夾的訪問,暫時沒做限制,但是,要訪問其中其他應(yīng)用的文件,必須允許用戶使用系統(tǒng)的文件選擇器應(yīng)用來選擇文件。
訪問其他應(yīng)用沙盒文件:如果你的應(yīng)用需要使用其他應(yīng)用在沙盒內(nèi)創(chuàng)建的文件,則需要點(diǎn)擊使用其他應(yīng)用的文件,。
所以請判斷當(dāng)應(yīng)用運(yùn)行在Q平臺上時,取消對READ_EXTERNAL_STORAGE 和 WRITE_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
}
}