Android
系統文件操做Android 4.4
及以上系統, 兼容AndroidQ
新的存儲策略Kotlin Sample
👉 appJava Sample
👉 sample_javabuild.gradle
:repositories {
maven { url 'https://dl.bintray.com/javakam/FileOperator' }
}
複製代碼
implementation 'ando.file:core:1.3.8' //核心庫必選(Core library required)
implementation 'ando.file:selector:1.3.8' //文件選擇器(File selector)
implementation 'ando.file:compressor:1.3.8' //圖片壓縮, 核心算法採用 Luban
implementation 'ando.file:android-q:1.3.8' //Q和11兼容庫,須要額外的庫:'androidx.documentfile:documentfile:1.0.1'
複製代碼
Application
中初始化(Initialization in Application)FileOperator.init(this,BuildConfig.DEBUG)
複製代碼
未用到反射, 不須要混淆。(No reflection is used, no need to be confused.)html
功能列表(Function list) | 緩存目錄(Cache directory) |
---|---|
![]() |
![]() |
單圖+壓縮(Single Image+Compress) | 多圖+壓縮(Multiple images+Compress) | 多文件+多類型(Multiple files+Multiple types) |
---|---|---|
![]() |
![]() |
![]() |
val optionsImage = FileSelectOptions().apply {
fileType = FileType.IMAGE
fileTypeMismatchTip = "文件類型不匹配 !" //File type mismatch
singleFileMaxSize = 5242880
singleFileMaxSizeTip = "圖片最大不超過5M !" //The largest picture does not exceed 5M
allFilesMaxSize = 10485760
allFilesMaxSizeTip = "總圖片大小不超過10M !"//The total picture size does not exceed 10M 注:單選條件下無效,只作單個圖片大小判斷
fileCondition = object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return (fileType == FileType.IMAGE && uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
}
}
mFileSelector = FileSelector
.with(this)
.setRequestCode(REQUEST_CHOOSE_FILE)
.setTypeMismatchTip("文件類型不匹配 !") //File type mismatch
.setMinCount(1, "至少選擇一個文件 !") //Choose at least one file
.setMaxCount(10, "最多選擇十個文件 !") //Choose up to ten files 注:單選條件下無效, 只作最少數量判斷
.setOverLimitStrategy(OVER_LIMIT_EXCEPT_OVERFLOW)
.setSingleFileMaxSize(1048576, "大小不能超過1M !") //The size cannot exceed 1M 注:單選條件下無效, FileSelectOptions.singleFileMaxSize
.setAllFilesMaxSize(10485760, "總大小不能超過10M !") //The total size cannot exceed 10M 注:單選條件下無效,只作單個圖片大小判斷 setSingleFileMaxSize
.setMimeTypes("image/*") //默認不作文件類型約束爲"*/*",不一樣類型系統提供的選擇UI不同 eg:"video/*","audio/*","image/*"
.applyOptions(optionsImage)
.filter(object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return when (fileType) {
FileType.IMAGE -> (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
FileType.VIDEO -> false
FileType.AUDIO -> false
else -> false
}
}
})
.callback(object : FileSelectCallBack {
override fun onSuccess(results: List<FileSelectResult>?) {
ResultUtils.resetUI(mTvResult)
if (results.isNullOrEmpty()) {
toastLong("沒有選取文件") //No file selected
return
}
showSelectResult(results)
}
override fun onError(e: Throwable?) {
FileLogger.e("FileSelectCallBack onError ${e?.message}")
ResultUtils.setErrorText(mTvError, e)
}
})
.choose()
複製代碼
Multiple selection pictures (multiple selection + single type)java
val optionsImage = FileSelectOptions().apply {
fileType = FileType.IMAGE
fileTypeMismatchTip = "文件類型不匹配 !" //File type mismatch
singleFileMaxSize = 5242880
singleFileMaxSizeTip = "單張圖片大小不超過5M !" //The size of a single picture does not exceed 5M
allFilesMaxSize = 10485760
allFilesMaxSizeTip = "圖片總大小不超過10M !" //The total size of the picture does not exceed 10M
fileCondition = object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return (fileType == FileType.IMAGE && uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
}
}
mFileSelector = FileSelector
.with(this)
.setRequestCode(REQUEST_CHOOSE_FILE)
.setMultiSelect()
.setMinCount(1, "至少選擇一個文件 !") //Choose at least one file
.setMaxCount(2, "最多選兩個文件!") //Choose up to two files
.setSingleFileMaxSize(3145728, "單個大小不能超過3M !") //Single size cannot exceed 3M
.setAllFilesMaxSize(20971520, "總文件大小不能超過20M !") //The total file size cannot exceed 20M
.setOverLimitStrategy(this.mOverLimitStrategy)
.setMimeTypes("image/*")
.applyOptions(optionsImage)
.filter(object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return (fileType == FileType.IMAGE) && (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
})
.callback(object : FileSelectCallBack {
override fun onSuccess(results: List<FileSelectResult>?) {
FileLogger.w("FileSelectCallBack onSuccess ${results?.size}")
mAdapter.setData(null)
if (results.isNullOrEmpty()) {
toastLong("沒有選取文件") //No file selected
return
}
showSelectResult(results)
}
override fun onError(e: Throwable?) {
FileLogger.e("FileSelectCallBack onError ${e?.message}")
ResultUtils.setErrorText(mTvError, e)
mAdapter.setData(null)
mBtSelect.text = "$mShowText (0)"
}
})
.choose()
複製代碼
Multiple files (multi-select multiple types)android
🌴適用於處理複雜文件選擇情形, 如: 選取圖片、音頻文件、文本文件, 其中
圖片
至少選擇一張, 最多選擇兩張, 每張圖片大小不超過5M, 所有圖片大小不超過10M;git
音頻文件
至少選擇兩個, 最多選擇三個, 每一個音頻大小不超過20M, 所有音頻大小不超過30M; 文本文件
至少選擇一個, 最多選擇兩個, 每一個文本文件大小不超過5M, 所有文本文件大小不超過10Mgithub
🌴It is suitable for processing complex file selection situations, such as: select pictures, audio files, text files, among which, select at least one picture and two at most. The size of each picture does not exceed 5M, and the size of all pictures does not exceed 10M;
audio File
Choose at least two and a maximum of three, each audio size does not exceed 20M, all audio size does not exceed 30M;text file
select at least one, select at most two, each text file size does not exceed 5M, all The text file size does not exceed 10Mweb
//圖片 Image
val optionsImage = FileSelectOptions().apply {
fileType = FileType.IMAGE
minCount = 1
maxCount = 2
minCountTip = "至少選擇一張圖片" //Select at least one picture
maxCountTip = "最多選擇兩張圖片" //Select up to two pictures
singleFileMaxSize = 5242880
singleFileMaxSizeTip = "單張圖片最大不超過5M !" //A single picture does not exceed 5M !
allFilesMaxSize = 10485760
allFilesMaxSizeTip = "圖片總大小不超過10M !" //The total size of the picture does not exceed 10M !
fileCondition = object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return (fileType == FileType.IMAGE && uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
}
}
//音頻 Audio
val optionsAudio = FileSelectOptions().apply {
fileType = FileType.AUDIO
minCount = 2
maxCount = 3
minCountTip = "至少選擇兩個音頻文件" //Select at least two audio files
maxCountTip = "最多選擇三個音頻文件" //Select up to three audio files
singleFileMaxSize = 20971520
singleFileMaxSizeTip = "單音頻最大不超過20M !" //Maximum single audio does not exceed 20M !
allFilesMaxSize = 31457280
allFilesMaxSizeTip = "音頻總大小不超過30M !" //The total audio size does not exceed 30M !
fileCondition = object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return (uri != null)
}
}
}
//文本文件 txt
val optionsTxt = FileSelectOptions().apply {
fileType = FileType.TXT
minCount = 1
maxCount = 2
minCountTip = "至少選擇一個文本文件" //Select at least one text file
maxCountTip = "最多選擇兩個文本文件" //Select at most two text files
singleFileMaxSize = 5242880
singleFileMaxSizeTip = "單文本文件最大不超過5M !" //The single biggest text file no more than 5M
allFilesMaxSize = 10485760
allFilesMaxSizeTip = "文本文件總大小不超過10M !" //Total size not more than 10M text file
fileCondition = object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return (uri != null)
}
}
}
/* 注:若是某個FileSelectOptions沒經過限定條件, 則該FileSelectOptions不會返回 eg: 採用上面的限制條件下,圖片、音頻、文本文件各選一個, 由於音頻最小數量設定爲`2`不知足設定條件則去除全部音頻選擇結果 , 因此返回結果中只有圖片和文本文件(限於OVER_LIMIT_EXCEPT_OVERFLOW) EN: Note: if a FileSelectOptions failed the qualification, then the FileSelectOptions will not return, Eg: using the restriction conditions, images, audio, text files, each choose a, because audio set the minimum amount as ` 2 ` set does not meet the conditions the choice of the results to remove all audio , Only pictures and text files, so return result (limited to OVER_LIMIT_EXCEPT_OVERFLOW); */
mFileSelector = FileSelector
.with(this)
.setRequestCode(REQUEST_CHOOSE_FILE)
.setMultiSelect() //默認是單選false (The default is radio false)
/* 實際最少數量限制爲 setMinCount 和 (optionsImage.minCount + optionsAudio.minCount +...) 中的最小值 實際最大數量限制爲 setMaxCount 和 (optionsImage.maxCount + optionsAudio.maxCount +...) 中的最大值, 因此此處的最大值限制是無效的 EN: Actual minimum limit for setMinCount and (optionsImage minCount optionsAudio. MinCount... The lowest value of), Actual maximum limit for setMaxCount and (optionsImage maxCount optionsAudio. MaxCount... ) the maximum, so the maximum limit here is invalid; */
.setMinCount(1, "設定類型文件至少選擇一個!") //Select at least one set type file
.setMaxCount(4, "最多選四個文件!") //Most alternative four files
/* 實際單文件大小限制爲 setSingleFileMaxSize 和 (optionsImage.singleFileMaxSize + optionsAudio.singleFileMaxSize +...) 中的最小值 實際總大小限制爲 setAllFilesMaxSize 和 (optionsImage.allFilesMaxSize + optionsAudio.allFilesMaxSize +...) 中的最大值 EN: Actual single file size limit for setSingleFileMaxSize and (optionsImage. SingleFileMaxSize optionsAudio. SingleFileMaxSize... The lowest value of), Actual total size limit for setAllFilesMaxSize and (optionsImage allFilesMaxSize optionsAudio. AllFilesMaxSize... The highest value in); */
//優先使用 `自定義FileSelectOptions` 中設置的單文件大小限制, 若是沒有設置則採用該值
//EN:Prefer using ` custom FileSelectOptions ` set in single file size limit, if the value is not set is used
.setSingleFileMaxSize(2097152, "單文件大小不能超過2M !") //The size of a single file cannot exceed 2M !
.setAllFilesMaxSize(52428800, "總文件大小不能超過50M !") //The total file size cannot exceed 50M !
//1. 文件超過數量限制或大小限制
//2. 單一類型: 保留未超限制的文件並返回, 去掉後面溢出的部分; 多種類型: 保留正確的文件, 去掉錯誤類型的全部文件
//EN:
//1. Documents more than limit or size limit
//2. Single type: keep not ultra limit file and return, get rid of the overflow part; Multiple types: keep the right file, get rid of the wrong type of all documents
.setOverLimitStrategy(this.mOverLimitStrategy)
//eg: ando.file.core.FileMimeType
//默認不作文件類型約束爲"*/*", 不一樣類型系統提供的選擇UI不同 eg: "video/*","audio/*","image/*"
//EN:Default do not file type constraints for "/", is not the same as the choice of different types of the system to provide the UI eg: "video/"," audio/", "image/"
.setMimeTypes("audio/*", "image/*", "text/plain")
//若是setMimeTypes和applyOptions沒對應上會出現`文件類型不匹配問題`
//EN:If setMimeTypes and applyOptions no corresponding will appear `file type mismatch problems`
.applyOptions(optionsImage, optionsAudio, optionsTxt)
//優先使用 FileSelectOptions 中設置的 FileSelectCondition
//EN:Priority in use FileSelectOptions FileSelectCondition Settings
.filter(object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return when (fileType) {
FileType.IMAGE -> (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
FileType.AUDIO -> true
FileType.TXT -> true
else -> false
}
}
})
.callback(object : FileSelectCallBack {
override fun onSuccess(results: List<FileSelectResult>?) {
FileLogger.w("FileSelectCallBack onSuccess ${results?.size}")
mAdapter.setData(null)
if (results.isNullOrEmpty()) {
toastLong("沒有選取文件") //No files selected
return
}
showSelectResult(results)
}
override fun onError(e: Throwable?) {
FileLogger.e("FileSelectCallBack onError ${e?.message}")
ResultUtils.setErrorText(mTvError, e)
mAdapter.setData(null)
mBtSelect.text = "$mShowText (0)"
}
})
.choose()
複製代碼
Extend existing FileType算法
eg:
內置(built in): TXT(mutableListOf("txt", "conf", "iml", "ini", "log", "prop", "rc"))
增長(increase): FileType.TXT.supplement("gradle","kt")
結果(result): TXT(mutableListOf("txt", "conf", "iml", "ini", "log", "prop", "rc","gradle","kt"))
移除(remove): FileType.TXT.remove("txt","ini")
結果(result): TXT(mutableListOf("conf", "iml", log", "prop", "rc")) 替換(replace): FileType.XML.replace("xxx") 調試(debugging): FileType.TXT.dump() 複製代碼
IFileType
自定義文件類型Through IFileType
custom file typeexpress
🍎下面提供了兩種實現的方式 (The following provides two ways):apache
//1.方式一
object FileTypePhp : IFileType {
override fun fromUri(uri: Uri?): IFileType {
return if (parseSuffix(uri).equals("php", true)) FileTypePhp else FileType.UNKNOWN
}
}
//2.推薦方式 (Recommended way)
enum class FileTypeJson : IFileType {
JSON;
override fun fromUri(uri: Uri?): IFileType {
return resolveFileMatch(uri, "json", JSON)
}
}
複製代碼
用法(Usage) :
val optionsJsonFile = FileSelectOptions().apply {
fileType = FileTypeJson.JSON
minCount = 1
maxCount = 2
minCountTip = "至少選擇一個JSON文件" //Choose at least one JSON file
maxCountTip = "最多選擇兩個JSON文件" //Choose up to two JSON files
}
FileSelector.with(this)
...
.setMimeTypes("audio/*", "image/*", "text/*", "application/json")
.applyOptions(optionsImage, optionsAudio, optionsTxt, optionsJsonFile)
.filter(object : FileSelectCondition {
override fun accept(fileType: IFileType, uri: Uri?): Boolean {
return when (fileType) {
FileType.IMAGE -> (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
FileType.AUDIO -> true
FileType.TXT -> true
FileTypeJson.JSON -> true
else -> false
}
}
})
.choose()
複製代碼
注意:
json
文件沒法用text/*
打開, 對應的mimeType
爲application/json
val bitmap:Bitmap=ImageCompressEngine.compressPure(uri)
複製代碼
/** * 壓縮圖片 1.Luban算法; 2.直接壓縮 -> val bitmap:Bitmap=ImageCompressEngine.compressPure(uri) * * T : String.filePath / Uri / File */
fun <T> compressImage(context: Context, photos: List<T>, success: (index: Int, uri: Uri?) -> Unit) {
ImageCompressor
.with(context)
.load(photos)
.ignoreBy(100)//Byte
.setTargetDir(getCompressedImageCacheDir())
.setFocusAlpha(false)
.enableCache(true)
.filter(object : ImageCompressPredicate {
override fun apply(uri: Uri?): Boolean {
//FileLogger.i("compressImage predicate $uri ${FileUri.getFilePathByUri(uri)}")
return if (uri != null) !FileUtils.getExtension(uri).endsWith("gif") else false
}
})
.setRenameListener(object : OnImageRenameListener {
override fun rename(uri: Uri?): String {
try {
val filePath = FileUri.getFilePathByUri(uri)
val md = MessageDigest.getInstance("MD5")
md.update(filePath?.toByteArray() ?: return "")
return BigInteger(1, md.digest()).toString(32)
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
}
return ""
}
})
.setImageCompressListener(object : OnImageCompressListener {
override fun onStart() {}
override fun onSuccess(index: Int, uri: Uri?) {
success.invoke(index, uri)
}
override fun onError(e: Throwable?) {
FileLogger.e("OnImageCompressListener onError ${e?.message}")
}
}).launch()
}
複製代碼
☘
FileOperator
提供了Android
開發經常使用的一些文件操做工具類,使用方式大多以靜態方法爲主,須要的同窗能夠直接CV須要的文件
File Name/Path/Url
獲取相應MimeType
fun getMimeType(str: String?): String {...}
fun getMimeType(uri: Uri?): String {...}
//MimeTypeMap.getSingleton().getMimeTypeFromExtension(...) 的補充
fun getMimeTypeSupplement(fileName: String): String {...}
複製代碼
文件/文件夾
大小(Get the size of the specified file folder
)@Throws(Exception::class)
fun getFolderSize(file: File?): Long {
var size = 0L
if (file == null || !file.exists()) return size
val files = file.listFiles()
if (files.isNullOrEmpty()) return size
for (i in files.indices) {
size += if (files[i].isDirectory) getFolderSize(files[i]) else getFileSize(files[i])
}
return size
}
複製代碼
fun getFileSize(file: File?): Long{...}
fun getFileSize(uri: Uri?): Long{...}
複製代碼
文件/文件夾
大小(Automatically calculate the size of the specified file folder
)自動計算指定文件或指定文件夾的大小 , 返回值帶 B、KB、M、GB、TB 單位的字符串
fun getFileOrDirSizeFormatted(path: String?): String {}...}
複製代碼
BigDecimal
實現)Format size (implemented by Big Decimal
)
/** * @param scale 精確到小數點之後幾位 (Accurate to a few decimal places) */
fun formatFileSize(size: Long, scale: Int, withUnit: Boolean = false): String {
val divisor = 1024L
//ROUND_DOWN 1023 -> 1023B ; ROUND_HALF_UP 1023 -> 1KB
val kiloByte: BigDecimal = formatSizeByTypeWithDivisor(BigDecimal.valueOf(size), scale, SIZE_TYPE_B, divisor)
if (kiloByte.toDouble() < 1) {
return "${kiloByte.toPlainString()}${if (withUnit) SIZE_TYPE_B.unit else ""}"
}
//KB
val megaByte = formatSizeByTypeWithDivisor(kiloByte, scale, SIZE_TYPE_KB, divisor)
if (megaByte.toDouble() < 1) {
return "${kiloByte.toPlainString()}${if (withUnit) SIZE_TYPE_KB.unit else ""}"
}
//M
val gigaByte = formatSizeByTypeWithDivisor(megaByte, scale, SIZE_TYPE_MB, divisor)
if (gigaByte.toDouble() < 1) {
return "${megaByte.toPlainString()}${if (withUnit) SIZE_TYPE_MB.unit else ""}"
}
//GB
val teraBytes = formatSizeByTypeWithDivisor(gigaByte, scale, SIZE_TYPE_GB, divisor)
if (teraBytes.toDouble() < 1) {
return "${gigaByte.toPlainString()}${if (withUnit) SIZE_TYPE_GB.unit else ""}"
}
//TB
return "${teraBytes.toPlainString()}${if (withUnit) SIZE_TYPE_TB.unit else ""}"
}
複製代碼
轉換文件大小,指定轉換的類型(Convert file size, specify the type of conversion):
//scale 精確到小數點之後幾位
fun formatSizeByTypeWithoutUnit(size: BigDecimal, scale: Int, sizeType: FileSizeType): BigDecimal =
size.divide(
BigDecimal.valueOf(when (sizeType) {
SIZE_TYPE_B -> 1L
SIZE_TYPE_KB -> 1024L
SIZE_TYPE_MB -> 1024L * 1024L
SIZE_TYPE_GB -> 1024L * 1024L * 1024L
SIZE_TYPE_TB -> 1024L * 1024L * 1024L * 1024L
}),
scale,
//ROUND_DOWN 1023 -> 1023B ; ROUND_HALF_UP 1023 -> 1KB
if (sizeType == SIZE_TYPE_B) BigDecimal.ROUND_DOWN else BigDecimal.ROUND_HALF_UP
)
複製代碼
轉換文件大小帶單位(Convert file size with unit):
fun formatSizeByTypeWithUnit(size: Long, scale: Int, sizeType: FileSizeType): String {
return "${formatSizeByTypeWithoutUnit(size.toBigDecimal(), scale, sizeType).toPlainString()}${sizeType.unit}"
}
複製代碼
fun openShare(context: Context, uri: Uri, title: String = "分享文件") {
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// Put the Uri and MIME type in the result Intent
intent.setDataAndType(uri, getMimeType(uri))
context.startActivity(Intent.createChooser(intent, title))
}
複製代碼
@SuppressLint("QueryPermissionsNeeded")
fun openBrowser( context: Context, url: String, title: String = "請選擇瀏覽器", newTask: Boolean = false, block: ((result: Boolean, msg: String?) -> Unit)? = null, ) {
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
//startActivity(intent)
//https://developer.android.com/about/versions/11/privacy/package-visibility
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(Intent.createChooser(intent, title))
block?.invoke(true, null)
} else {
block?.invoke(true, "沒有可用瀏覽器")
}
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
block?.invoke(true, e.toString())
}
}
複製代碼
Url
對應的系統應用Directly open the system application corresponding to Url
eg: 若是url是視頻地址,則直接用系統的播放器打開
fun openUrl(activity: Activity, url: String?) {
try {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.parse(url), getMimeType(url))
activity.startActivity(intent)
} catch (e: Exception) {
FileLogger.e("openUrl error : " + e.message)
}
}
複製代碼
文件路徑
和類型(後綴判斷)
顯示支持該格式的程序According to file path
and type (judgment by suffix)
show programs that support the format
fun openFile(context: Any, uri: Uri?, mimeType: String? = null) =
uri?.let { u ->
Intent.createChooser(createOpenFileIntent(u, mimeType), "選擇程序")?.let {
startActivity(context, it)
}
}
複製代碼
Select file [Use system file management]
/** * ### 選擇文件【調用系統的文件管理】 (Select file [call system file management]) * * 注: * * #### 1. Intent.setType 不能爲空(Can not be empty) ! * ``` * android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] (has extras) } * at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2105) * ``` * * #### 2. mimeTypes 會覆蓋 mimeType (mimeTypes will override mimeType) * ``` * eg: * Intent.setType("image / *") * Intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("audio / *")) * 🍎 最終可選文件類型變爲音頻 * ``` * * #### 3. ACTION_GET_CONTENT, ACTION_OPEN_DOCUMENT 效果相同, Android Q 上使用 `ACTION_GET_CONTENT` 會出現: * ``` * java.lang.SecurityException: UID 10483 does not have permission to content://com.android.providers.media.documents/document/image%3A16012 [user 0]; * you could obtain access using ACTION_OPEN_DOCUMENT or related APIs * ``` * * #### 4. 開啓多選(Open multiple selection) resultCode = -1 * * #### 5. 不管是`ACTION_OPEN_DOCUMENT`仍是`ACTION_GET_CONTENT`都只是負責打開和選擇, * 具體的文件操做如查看文件內容,刪除,分享,複製,重命名等操做須要在`onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)`中的`data:Intent`中提取 * */
fun createChooseIntent(@NonNull mimeType: String?, @Nullable mimeTypes: Array<String>?, multiSelect: Boolean): Intent =
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiSelect)
type = if (mimeType.isNullOrBlank()) "*/*" else mimeType
if (!mimeTypes.isNullOrEmpty()) {
putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
}
addCategory(Intent.CATEGORY_OPENABLE)
}
複製代碼
File
路徑中獲取Uri
Obtain Uri
from File
path
fun getUriByPath(path: String?): Uri? = if (path.isNullOrBlank()) null else getUriByFile(File(path))
fun getUriByFile(file: File?): Uri? =
file?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val authority = FileOperator.getContext().packageName + PATH_SUFFIX
FileProvider.getUriForFile(FileOperator.getContext(), authority, file)
} else Uri.fromFile(file)
}
複製代碼
Uri
對應的文件路徑,兼容API 26
Get the file path corresponding to Uri
, compatible with API 26
fun getFilePathByUri(context: Context?, uri: Uri?): String? {
if (context == null || uri == null) return null
val scheme = uri.scheme
// 以 file:// 開頭的使用第三方應用打開
if (ContentResolver.SCHEME_FILE.equals(scheme, ignoreCase = true)) return uri.path
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) getPath(context, uri) else getPathKitkat(context, uri)
}
複製代碼
Method | Remark |
---|---|
getExtension |
獲取文件後綴jpg |
getExtensionFull |
獲取文件完整後綴.jpg |
splitFilePath() |
拆分文件路徑 eg: /xxx/xxx/note.txt 👉 path : /xxx/xxx (注:尾部沒有/ ) name : note suffix : txt |
getFileNameFromPath(path: String?) |
經過FilePath 獲取文件名 |
getFileNameFromUri(uri: Uri?) |
經過Uri 獲取文件名 |
createFile(filePath: String?, fileName: String?, overwrite: Boolean = false):File? |
建立文件, 同名文件建立屢次會跳過已有建立新的文件,如:note.txt已存在,則再次建立會生成note(1).txt |
createDirectory(filePath: String?): Boolean |
建立目錄 |
deleteFile |
刪除文件或目錄 |
deleteFileWithoutExcludeNames(file: File?, vararg excludeDirs: String?) |
刪除文件或目錄, excludeDirs 指定名稱的一些文件/文件夾 不作刪除 |
deleteFilesNotDir |
只刪除文件,不刪除文件夾 |
readFileText(InputStream/Uri): String? |
讀取文本文件中的內容 |
readFileBytes(InputStream/Uri): ByteArray? |
讀取文件中的內容並返回ByteArray |
copyFile |
根據文件路徑拷貝文件 java.nio |
write2File(bitmap:Bitmap, file:File?, overwrite:Boolean=false) |
把Bitmap 寫到文件中,可經過BitmapFactory.decodeStream() 讀取出來 |
write2File(input:InputStream?, file:File?, overwrite:Boolean=false) |
向文件中寫入數據 |
isLocal |
檢驗是否爲本地URI |
isGif() |
檢驗是否爲 gif |
copyFile
效率和kotlin-stdlib-1.4.21.jar
中的kotlin.io.FilesKt__UtilsKt.copyTo
基本至關 :
fun File.copyTo(target: File, overwrite: Boolean = false,bufferSize: Int = DEFAULT_BUFFER_SIZE): File
複製代碼
Usage:
boolean copyResult = FileUtils.copyFile(fileOld, getExternalFilesDir(null).getPath(), "test.txt");
File targetFile = new File(getExternalFilesDir(null).getPath() + "/" + "test.txt");
複製代碼
onActivityResult
中要把選擇文件的結果交給FileSelector
處理 :override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
//選擇結果交給 FileSelector 處理, 可經過`requestCode -> REQUEST_CHOOSE_FILE`進行區分
mFileSelector?.obtainResult(requestCode, resultCode, data)
}
複製代碼
選擇文件不知足預設條件時,有兩種策略 :
OVER_LIMIT_EXCEPT_ALL 文件超過數量或大小
限制直接返回失敗, 回調 onError
OVER_LIMIT_EXCEPT_OVERFLOW ① 文件超過數量限制或大小限制;
② 單一類型: 保留未超限制的文件並返回, 去掉後面溢出的部分; 多種類型: 保留正確的文件, 去掉錯誤類型的全部文件; ③ 回調 onSuccess
選擇文件數據:單選 Intent.getData ; 多選 Intent.getClipData
Android 系統問題 : Intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
開啓多選條件下只選擇一個文件時,系統是按照單選邏輯走的... Σ( ° △ °|||)︴
Activity
中執行getExternalFilesDirs(Environment.DIRECTORY_XXX)
和其它獲取目錄地址的方法時,都會自動建立相應的目錄Uri.fromFile(file)
生成的file:///...
是不能分享的,因此須要使用FileProvider
將App Specific
目錄下的文件分享給其餘APP讀寫,須要經過FileProvider
解析出的可用於分享的路徑: ando.file.core.FileUri.getUriByFile(file)
README_VERSIONS.md github.com/javakam/Fil…
W/ExifInterface: Invalid image: ExifInterface got an unsupported image format
file(ExifInterface supports JPEG and some RAW image formats only) or a corrupted JPEG file to ExifInterface.
java.io.IOException: Invalid byte order: 0
at android.media.ExifInterface.readByteOrder(ExifInterface.java:3134)
at android.media.ExifInterface.isOrfFormat(ExifInterface.java:2449)
at android.media.ExifInterface.getMimeType(ExifInterface.java:2327)
at android.media.ExifInterface.loadAttributes(ExifInterface.java:1755)
at android.media.ExifInterface.<init>(ExifInterface.java:1449)
...
Fixed :
dependencies {
compileOnly "androidx.exifinterface:exifinterface:1.3.2"
...
}
Then replace `android.media.ExifInterface` with `androidx.exifinterface.media.ExifInterface`
複製代碼
Caused by: android.graphics.ImageDecoder$DecodeException:
Failed to create image decoder with message 'unimplemented'Input contained an error.
複製代碼
What is new in Android P — ImageDecoder & AnimatedImageDrawable
java.lang.SecurityException: UID 10483 does not have permission to
content://com.android.providers.media.documents/document/image%3A16012 [user 0];
you could obtain access using ACTION_OPEN_DOCUMENT or related APIs
複製代碼
Fixed:
ando.file.core.FileOpener.createChooseIntent
把 Intent(Intent.ACTION_GET_CONTENT) 改成 Intent(Intent.ACTION_OPEN_DOCUMENT)
stackoverflow.com/questions/4…
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] (has extras) }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2105)
複製代碼
Fixed:
ando.file.core.FileOpener.createChooseIntent
:
Intent.setType("image / *")
Intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("audio / *"))
複製代碼
Fixed:
AndroidManifest.xml
沒配置FileProvider
stackoverflow.com/questions/3…
Fixed:
Intent.createChooser
要添加兩次FLAG_ACTIVITY_NEW_TASK
:
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val chooserIntent: Intent = Intent.createChooser(intent, title)
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(chooserIntent)
複製代碼
管理分區外部存儲訪問 - 如何從原生代碼訪問媒體文件 & MediaStore增刪該查API
Copyright 2019 javakam, FileOperator Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
複製代碼