如果项目完全没有适配Android10 可以使用该方法通用Android10 沙盒机制
requestLegacyExternalStorage=ture
android:maxSdkVersion="28" //最大
分区存储
https://blog.csdn.net/mr_lichao/article/details/107516514
存储权限
Android Q
在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。
沙盒,简单而言就是应用专属文件夹,并且访问这个文件夹无需权限。谷歌官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()
下的文件夹。
比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
中。
以下将按访问的目标文件的地址介绍如何适配。
-
访问自己文件:Q中用更精细的媒体特定权限替换并取消了
READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
权限,并且无需特定权限,应用即可访问自己沙盒中的文件。 -
访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问
沙盒外的媒体共享文件
,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO
,READ_MEDIA_AUDIO
,申请方法同原来的存储权限。 -
访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。
-
访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,则需要点击使用其他应用的文件,。
所以请判断当应用运行在Q平台上时,取消对READ_EXTERNAL_STORAGE
和 WRITE_EXTERNAL_STORAGE
两个权限的申请。并替换为新的媒体特定权限。
遇到的问题
1.Android10 申请不到 WRITE_EXTERNAL_STORAGE 失败
2.在得到图片,视频 路径拿不到SD卡视频、图片 结果没有权限
解决办法
1.将系统返回Uri 文件复制到自己项目内这样就不需要权限了
var originPath: String? = ""
//这里需要做版本判断如果Android10 以下返回的真实的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 {
//否则列出目录中的所有文件递归进行压缩
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
// 循环着把所有的异常信息写入writer中
while (cause != null) {
cause.printStackTrace(pw)
cause = cause.cause
}
pw.close() // 记得关闭
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)
//创建临时文件夹
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
}
}
网友评论