上一篇 👉 Android Q & Android 11存儲適配(一) 基礎知識點梳理java
implementation 'com.ando.file:FileOperator:0.8.0'
複製代碼
Application.onCreated
FileOperator.init(this,BuildConfig.DEBUG)
複製代碼
val optionsImage = FileSelectOptions()
optionsImage.fileType = FileType.IMAGE
options.mMinCount = 0
options.mMaxCount = 10
optionsImage.mSingleFileMaxSize = 2097152 // 20M = 20971520 B
optionsImage.mSingleFileMaxSizeTip = "圖片最大不超過2M!"
optionsImage.mAllFilesMaxSize = 5242880 //5M 5242880 ; 20M = 20971520 B
optionsImage.mAllFilesMaxSizeTip = "總圖片大小不超過5M!"
optionsImage.mFileCondition = object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
return (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
}
mFileSelector = FileSelector
.with(this)
.setRequestCode(REQUEST_CHOOSE_FILE)
.setSelectMode(false)
.setMinCount(1, "至少選一個文件!")
.setMaxCount(10, "最多選十個文件!")
.setSingleFileMaxSize(5242880, "大小不能超過5M!") //5M 5242880 ; 100M = 104857600 KB
.setAllFilesMaxSize(10485760, "總大小不能超過10M!")//
.setMimeTypes(MIME_MEDIA)//默認所有文件, 不一樣 arrayOf("video/*","audio/*","image/*") 系統提供的選擇UI不同
.applyOptions(optionsImage)
//優先使用 FileOptions 中設置的 FileSelectCondition
.filter(object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
when (fileType) {
FileType.IMAGE -> {
return (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
FileType.VIDEO -> true
FileType.AUDIO -> true
else -> true
}
return true
}
})
.callback(object : FileSelectCallBack {
override fun onSuccess(results: List<FileSelectResult>?) {
FileLogger.w("回調 onSuccess ${results?.size}")
mTvResult.text = ""
if (results.isNullOrEmpty()) return
shortToast("正在壓縮圖片...")
showSelectResult(results)
}
override fun onError(e: Throwable?) {
FileLogger.e("回調 onError ${e?.message}")
mTvResultError.text = mTvResultError.text.toString().plus(" 錯誤信息: ${e?.message} \n")
}
})
.choose()
複製代碼
val optionsImage = FileSelectOptions()
optionsImage.fileType = FileType.IMAGE
options.mMinCount = 0
options.mMaxCount = 10
optionsImage.mSingleFileMaxSize = 3145728 // 20M = 20971520 B
optionsImage.mSingleFileMaxSizeTip = "單張圖片最大不超過3M!"
optionsImage.mAllFilesMaxSize = 5242880 //3M 3145728 ; 5M 5242880 ; 10M 10485760 ; 20M = 20971520 B
optionsImage.mAllFilesMaxSizeTip = "圖片總大小不超過5M!"
optionsImage.mFileCondition = object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
return (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
}
mFileSelector = FileSelector
.with(this)
.setRequestCode(REQUEST_CHOOSE_FILE)
.setSelectMode(true)
.setMinCount(1, "至少選一個文件!")
.setMaxCount(10, "最多選十個文件!")
//優先以自定義的 optionsImage.mSingleFileMaxSize 爲準5M 5242880 ; 100M = 104857600 KB
.setSingleFileMaxSize(2097152, "大小不能超過2M!")
.setAllFilesMaxSize(20971520, "總文件大小不能超過20M!")
//1.OVER_SIZE_LIMIT_ALL_DONT 超過限制大小所有不返回 ;2.OVER_SIZE_LIMIT_EXCEPT_OVERFLOW_PART 超過限制大小去掉後面相同類型文件
.setOverSizeLimitStrategy(this.mOverSizeStrategy)
.setMimeTypes(MIME_MEDIA)//默認所有文件, 不一樣 arrayOf("video/*","audio/*","image/*") 系統提供的選擇UI不同
.applyOptions(optionsImage)
//優先使用 FileOptions 中設置的 FileSelectCondition
.filter(object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
when (fileType) {
FileType.IMAGE -> {
return (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
FileType.VIDEO -> true
FileType.AUDIO -> true
else -> true
}
return true
}
})
.callback(object : FileSelectCallBack {
override fun onSuccess(results: List<FileSelectResult>?) {
FileLogger.w("回調 onSuccess ${results?.size}")
mTvResult.text = ""
if (results.isNullOrEmpty()) return
shortToast("正在壓縮圖片...")
showSelectResult(results)
}
override fun onError(e: Throwable?) {
FileLogger.e("回調 onError ${e?.message}")
mTvResultError.text = mTvResultError.text.toString().plus(" 錯誤信息: ${e?.message} \n")
}
})
.choose()
複製代碼
🌴適用於處理複雜文件選擇情形,如: 選取圖片、視頻文件,其中圖片至少選擇一張,最多選擇兩張,每張圖片大小不超過3M,所有圖片大小不超過5M ; 視頻文件只能選擇一個, 每一個視頻大小不超過20M,所有視頻大小不超過30M。android
//圖片
val optionsImage = FileSelectOptions().apply {
fileType = FileType.IMAGE
mMinCount = 1
mMaxCount = 2
mMinCountTip = "至少選擇一張圖片"
mMaxCountTip = "最多選擇兩張圖片"
mSingleFileMaxSize = 3145728 // 20M = 20971520 B
mSingleFileMaxSizeTip = "單張圖片最大不超過3M!"
mAllFilesMaxSize = 5242880 // 5M 5242880
mAllFilesMaxSizeTip = "圖片總大小不超過5M!"
mFileCondition = object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
return (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
}
}
//視頻
val optionsVideo = FileSelectOptions().apply {
fileType = FileType.VIDEO
mMinCount = 1
mMaxCount = 1
mMinCountTip = "至少選擇一個視頻文件"
mMaxCountTip = "最多選擇一個視頻文件"
mSingleFileMaxSize = 20971520 // 20M = 20971520 B
mSingleFileMaxSizeTip = "單視頻最大不超過20M!"
mAllFilesMaxSize = 31457280 //3M 3145728
mAllFilesMaxSizeTip = "視頻總大小不超過30M!"
mFileCondition = object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
return (uri != null)
}
}
}
mFileSelector = FileSelector
.with(this)
.setRequestCode(REQUEST_CHOOSE_FILE)
.setSelectMode(true)
.setMinCount(1, "至少選一個文件!")
.setMaxCount(5, "最多選五個文件!")
// 優先使用自定義 FileSelectOptions 中設置的單文件大小限制,若是沒有設置則採用該值
// 100M = 104857600 KB ;80M 83886080 ;50M 52428800 ; 20M 20971520 ;5M 5242880 ;
.setSingleFileMaxSize(2097152, "單文件大小不能超過2M!")
.setAllFilesMaxSize(52428800, "總文件大小不能超過50M!")
// 超過限制大小兩種返回策略: 1.OVER_SIZE_LIMIT_ALL_DONT,超過限制大小所有不返回;2.OVER_SIZE_LIMIT_EXCEPT_OVERFLOW_PART,超過限制大小去掉後面相同類型文件
.setOverSizeLimitStrategy(OVER_SIZE_LIMIT_EXCEPT_OVERFLOW_PART)
.setMimeTypes(null)//默認爲 null,*/* 即不作文件類型限定; MIME_MEDIA 媒體文件, 不一樣 arrayOf("video/*","audio/*","image/*") 系統提供的選擇UI不同
.applyOptions(optionsImage, optionsVideo)
// 優先使用 FileOptions 中設置的 FileSelectCondition , 沒有的狀況下才使用通用的
.filter(object : FileSelectCondition {
override fun accept(fileType: FileType, uri: Uri?): Boolean {
when (fileType) {
FileType.IMAGE -> {
return (uri != null && !uri.path.isNullOrBlank() && !FileUtils.isGif(uri))
}
FileType.VIDEO -> true
FileType.AUDIO -> true
else -> true
}
return true
}
})
.callback(object : FileSelectCallBack {
override fun onSuccess(results: List<FileSelectResult>?) {
FileLogger.w("回調 onSuccess ${results?.size}")
mTvResult.text = ""
if (results.isNullOrEmpty()) return
showSelectResult(results)
}
override fun onError(e: Throwable?) {
FileLogger.e("回調 onError ${e?.message}")
mTvResultError.text = mTvResultError.text.toString().plus(" 錯誤信息: ${e?.message} \n")
}
})
.choose()
複製代碼
//T 爲 String.filePath / Uri / File
fun <T> compressImage(photos: List<T>) {
ImageCompressor
.with(this)
.load(photos)
.ignoreBy(100)//B
.setTargetDir(getPathImageCache())
.setFocusAlpha(false)
.enableCache(true)
.filter(object : ImageCompressPredicate {
override fun apply(uri: Uri?): Boolean {
//getFilePathByUri(uri)
FileLogger.i("image predicate $uri ${getFilePathByUri(uri)}")
return if (uri != null) {
val path = getFilePathByUri(uri)
!(TextUtils.isEmpty(path) || (path?.toLowerCase()
?.endsWith(".gif") == true))
} else {
false
}
}
})
.setRenameListener(object : OnImageRenameListener {
override fun rename(uri: Uri?): String? {
try {
val filePath = 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(uri: Uri?) {
val path = "$cacheDir/image/"
FileLogger.i("compress onSuccess uri=$uri path=${uri?.path} 緩存目錄總大小=${FileSizeUtils.getFolderSize(File(path))}")
val bitmap = getBitmapFromUri(uri)
dumpMetaData(uri) { displayName: String?, size: String? ->
runOnUiThread {
mTvResult.text = mTvResult.text.toString().plus(
"\n ---------\n👉壓縮後 \n Uri : $uri \n 路徑: ${uri?.path} \n 文件名稱 :$displayName \n 大小:$size B \n" +
"格式化 : ${FileSizeUtils.formatFileSize(size?.toLong() ?: 0L)}\n ---------"
)
}
}
mIvCompressed.setImageBitmap(bitmap)
}
override fun onError(e: Throwable?) {
FileLogger.e("compress onError ${e?.message}")
}
}).launch()
}
複製代碼
fun getUriByPath(path: String?): Uri? = if (path.isNullOrBlank()) null else getUriByFile(File(path))
fun getUriByFile(file: File?): Uri? {
if (file == null) return null
return 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)
}
}
複製代碼
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)) {//使用第三方應用打開
uri.path
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //4.4之後
getPath(context, uri)
} else { //4.4如下
getPathKitkat(context, uri)
}
}
複製代碼
jpg
.jpg
目錄/文件
String
ByteArray
eg :boolean copyFile = FileUtils.copyFile(fileOld, "/test_" + i, getExternalFilesDir(null).getPath());
File fileNew =new File( getExternalFilesDir(null).getPath() +"/"+ "test_" + i);
複製代碼
onActivityResult
中要把選擇文件的結果交給FileSelector
處理mFileSelector?.obtainResult(requestCode, resultCode, data)
git
選擇文件不知足預設條件時,有兩種策略 :github
1.當設置總文件大小限制時,有兩種策略 OVER_SIZE_LIMIT_ALL_DONT 只要有一個文件超出直接返回 onErrorc#
2.OVER_SIZE_LIMIT_EXCEPT_OVERFLOW_PART 去掉超過限制大小的溢出部分的文件segmentfault
選擇文件數據:單選 Intent.getData ; 多選 Intent.getClipData緩存
Android 系統問題 : Intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) 開啓多選條件下只選擇一個文件時,須要安裝單選邏輯走... Σ( ° △ °|||)︴bash
回調處理app
多選模式下,建議使用統一的 CallBack 回調;
單選模式下,若是配置了自定義的 CallBack , 則優先使用該回調;不然使用統一的 CallBack框架
eg:Activity 👉 getFilesDir or getCacheDir
例如建立壓縮圖片的臨時緩存目錄:
val path = "$filesDir/temp/image/"
複製代碼
1.作一個自定義UI的文件管理器
2.增長Fragment使用案例 , 視頻壓縮-郭笑醒 , 清除緩存功能 , 外置存儲適配
3.整理更詳細的文檔 配合 com.liulishuo.okdownload 作文件下載 👉 library_file_downloader
4.
複製代碼
管理分區外部存儲訪問 管理分區外部存儲訪問 - 如何從原生代碼訪問媒體文件 & MediaStore增刪該查API
項目基於 OkDownload 實現
斷點異常的BUG github.com/lingochamp/…
Simple github.com/lingochamp/…
Advanced github.com/lingochamp/…
AndroidFilePicker github.com/rosuH/Andro…
FilePicker github.com/chsmy/FileP…