安卓 文件管理相关功能记录
文件管理细分为图片、视频、音乐、文件四类
目录
权限
静态声明权限
动态检查和声明权限方法
如何开始上述动态申请的流程
提示
图片
获取图片文件的对象列表
展示
删除
视频
获取视频文件的对象列表
获取视频file列表
按日期装载视频文件列表
展示
播放
删除
音乐
工具类
获取音乐视频文件的对象列表
播放
删除
文件
获取文件对象列表
展示
点击打开
删除
不管是哪种都需要权限,关于安卓的文件访问权限不同版本有不同的管理方式,下面的代码不一定通用
权限
静态声明权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
动态检查和声明权限方法
//判断是否拥有权限
private fun hasPermission() :Boolean {return ContextCompat.checkSelfPermission(requireContext(),Manifest.permission.WRITE_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED&& ContextCompat.checkSelfPermission(requireContext(),Manifest.permission.READ_EXTERNAL_STORAGE ) == PackageManager.PERMISSION_GRANTED}//检查权限 23以前在app安装时静态声明的权限已被授予,不用动态授予
private fun checkHasPermission(jump:Runnable) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (!hasPermission()) requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) else jump.run()} else {jump.run()}}//下面的两个方法是不同的版本申请权限的方法//请求权限
private fun requestPermission(vararg requestPermission: String) =ActivityCompat.requestPermissions(requireActivity(), requestPermission, 2024080202)//到文件管理权限授予界面private fun toFileManagerPage() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {if (!Environment.isExternalStorageManager()){val appIntent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)appIntent.data = Uri.parse("package:" + AppUtils.getAppPackageName())try {requireActivity().startActivityForResult(appIntent,2024080201)} catch (ex: ActivityNotFoundException) {ex.printStackTrace()val allFileIntent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)requireActivity().startActivityForResult(allFileIntent,2024080201)}}else{LogUtils.d("RequestAccessAndroidDataUtils","已授予所有文件的权限")}}}//下面的两个方法,是上面两种申请的回调,且下面的两个方法得在activity中才有,如果别的几个方法在fragment中,就使用Livedatabus 将结果发送出来,之后在需要接收的地方接收就行@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);//将权限申请结果广播出去LiveDataBus.getInstance().with("requestCode").setValue(requestCode+","+resultCode);}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);//由于可能有多个权限申请,按照逻辑与处理int resultCode = PackageManager.PERMISSION_GRANTED; //默认权限已授予for (int i = 0; i < grantResults.length; i++) {if (grantResults[i] != PackageManager.PERMISSION_GRANTED){resultCode = PackageManager.PERMISSION_DENIED;break;}}//将权限申请结果广播出去LiveDataBus.getInstance().with("requestCode").setValue(requestCode+","+resultCode);}//下面的代码就是接收上面发送的权限申请结果//接受权限申请回调,数据来源于HomeActivity的onRequestPermissionsResult()和onActivityResult()LiveDataBus.getInstance().with("requestCode", String::class.java).observe(requireActivity()) {val result = it.split(",")if (result[0].equals("2024080201") && result[1].toInt() == PackageManager.PERMISSION_GRANTED){//文件管理权限回调}else if(result[0].equals("2024080202") && result[1].toInt() ==PackageManager.PERMISSION_GRANTED){//文件访问权限回调}}//入口方法
private fun callPermission(runnable: Runnable) {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Environment.isExternalStorageManager()) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) checkHasPermission(runnable) else runnable.run()} else {val builder = AlertDialog.Builder(requireContext()).setTitle(R.string.zfile_11_title).setMessage("管理文件需要您同意“允许所有文件管理权限”才能正常使用!").setCancelable(false).setPositiveButton(R.string.zfile_down) { d, _ ->toFileManagerPage()d.dismiss()}.setNegativeButton(R.string.zfile_cancel) { d, _ ->d.dismiss()}builder.show()}}
注:没有LivedataBus的可以去看另一篇博客:
安卓LiveDataBus使用记录-CSDN博客
如何开始上述动态申请的流程
binding.noPermissionLayout.setOnClickListener {callPermission{//这里就是拥有权限后需要进行的操作}
}
好了,搞完权限我们来具体到每个分类
由于项目java和kotlin混用,我就不自己转了,需要的自己转化一下吧
提示
下面分类中所有获取或者删除相关文件的代码都最好放到子线程或者协程的io线程中去,免得堵塞主线程
图片
先创建图片文件的bean对象来承载数据
data class FileEntity(var size: String,var path: String,var isSelect: Boolean = false,var displayName: String = "",var time: String = ""
) : Serializable {constructor(size: String, path: String) : this(size, path, false, "", "")
}
获取图片文件的对象列表
最好在子线程中
List<FileEntity> mediaBeen = new ArrayList<>();Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;String[] projImage = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA, MediaStore.Images.Media.SIZE, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN};Cursor mCursor = null;try {mCursor = mActivity.getContentResolver().query(mImageUri,projImage,MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?"+ "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",new String[]{"image/jpeg", "image/png","image/jpg"},MediaStore.Images.Media.DATE_MODIFIED + " desc");} catch (Throwable t) {}if (mCursor != null) {while (mCursor.moveToNext()) {// 获取图片的路径String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));int size = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Images.Media.SIZE));String displayName = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));Long date = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN));SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//用于展示相册初始化界面mediaBeen.add(new FileEntity(size + "", path, displayName,dateFormat.format(new Date(date))));}mCursor.close();}
//获取到所有图片的列表之后就是对于图片的展示和删除等
展示
//展示第一张图片到ImageView中
String path = listImage.get(0).getPath();//使用Glide加载本地图片Glide.with(mActivity).load(path).error(R.mipmap.error).listener(new RequestListener<Drawable>() {@Overridepublic boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {Log.e("ImagePositionPath","error: "+e.getMessage());/* Toast.makeText(mActivity, e.getMessage(), Toast.LENGTH_SHORT).show();*/return false;}@Overridepublic boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {return false;}}).into(((ImageViewHolder) holder).iv_photo_filelist_pic);
删除
最好也在子线程中删除
private void deletePicture(String localPath, Context context) {if(!TextUtils.isEmpty(localPath)){Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;ContentResolver contentResolver = context.getContentResolver();String url = MediaStore.Images.Media.DATA + "=?";int deleteRows = contentResolver.delete(uri, url, new String[]{localPath});if (deleteRows == 0) {//当生成图片时没有通知(插入到)媒体数据库,那么在图库里面看不到该图片,而且使用contentResolver.delete方法会返回0,此时使用file.delete方法删除文件File file = new File(localPath);if (file.exists()) {file.delete();}}}}//使用
deletePicture(listImage.get(0).getPath(),context);
视频
先创建视频文件的bean对象来承载数据
public class VideoInfoBean implements Serializable {//日期public String date;//文件名public String name;//文件路径public String path="";//文件大小public long packageSize;//是否可选择public boolean isSelect;/*** 标题为0,内容为1*/public int itemType;
}
某一日视频文件的bean对象
public class VideoFileCollenctionBean implements Serializable {//保存的是年月日public String date;/*** 本日对应的视频文件集合*/public List<VideoInfoBean> lists=new ArrayList<>();
}
获取视频文件的对象列表
获取视频file列表
下面的代码还是最好放在子线程中
String path = Environment.getExternalStorageDirectory().getPath();private List<File> files = new ArrayList<>();/*** mp4 mov mkv avi wmv m4v mpg vob webm ogv 3gp flv f4v swf gif* @param path*/private void scanViodeFile(String path){File file = new File(path);if (file.isDirectory()) {File[] f = file.listFiles();if (null != f) {for (File file1 : f) {String fileName=file1.getName().toLowerCase();if (file1.isDirectory()) {scanViodeFile(path + "/" + file1.getName());} else if (fileName.endsWith(".mp4")||fileName.equals(".mov")|| fileName.equals(".mkv")||fileName.equals(".avi")||fileName.equals(".wmv")||fileName.equals(".m4v")||fileName.equals(".mpg")||fileName.equals(".vob")||fileName.equals(".webm")||fileName.equals(".ogv")||fileName.equals(".3gp")||fileName.equals(".flv")||fileName.equals(".f4v")||fileName.equals(".swf")&& file.length()!=0) {files.add(file1);}}}}}
按日期装载视频文件列表
处理上面的file列表,将信息装载为VideoFileCollenctionBean 列表,用于和系统相册一样按天展示所有的视频信息
//装载的列表
List<VideoFileCollenctionBean> lists=new ArrayList<>();//使用集合的不可重复性,找出所有拥有视频的日期
Set<String> set=new TreeSet<String>((o1, o2) -> o2.compareTo(o1));
for (File file : files) {//将date转换为年月日的字符串String time= DateUtils.timestampToPatternTime(file.lastModified(), "yyyy-MM-dd");set.add(time);
}for (String l : set) {VideoFileCollenctionBean videoFileCollenctionBean = new VideoFileCollenctionBean();lists.add(videoFileCollenctionBean);VideoInfoBean videoInfoBean=new VideoInfoBean();videoInfoBean.date=l;videoInfoBeans.add(videoInfoBean);for (File file : files) {String time = DateUtils.timestampToPatternTime(file.lastModified(), "yyyy-MM-dd");if (time.equals(l)) {VideoInfoBean bean = new VideoInfoBean();bean.path = file.getPath();bean.name = file.getName();bean.date=time;bean.packageSize = file.length();bean.itemType=1;videoFileCollenctionBean.lists.add(bean);videoInfoBeans.add(bean);}}
}
展示

展示一般就是用adapter显示视频列表,按上图所示展示,红框就是一个VideoFileCollenctionBean对象,绿框就是VideoInfoBean对象,细节不说,仅说明一下关键部分
下面的部分应该在adapter的onBindViewHolder()中
//展示视频第一帧图片图片private void setImgFrame(ImageView imgFrame, String path) {RequestOptions options = new RequestOptions().placeholder(R.color.color_666666)// 正在加载中的图片.diskCacheStrategy(DiskCacheStrategy.ALL); // 磁盘缓存策略Glide.with(mContext).load(path).apply(options).into(imgFrame);}//使用@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {VideoInfoBean appInfoBean = getLists().get(position);setImgFrame(viewHolder.mImgFrame, appInfoBean.path);
}
播放
public void play(VideoInfoBean appInfoBean) {try {if (appInfoBean.path == null){return;}String filePath = appInfoBean.path;if (filePath.isEmpty()) return;File file = new File(filePath);if (!file.exists()) {// 文件不存在,可以在这里处理错误return;}// 获取文件 MIME 类型String mimeType = getMimeType(file);// 检查 MIME 类型是否为视频类型if (!isVideoType(mimeType)) {// 不是视频文件,可以在这里处理错误return;}// 创建 UriUri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);// 创建 IntentIntent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(uri, mimeType);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);// 检查是否有应用可以处理此意图if (intent.resolveActivity(getPackageManager()) != null) {startActivity(intent);} else {Toast.makeText(this, "当前系统无可播放视频的软件!", Toast.LENGTH_SHORT );}} catch (IllegalStateException e) {e.printStackTrace();}}private boolean isVideoType(String mimeType) {return mimeType.startsWith("video/");}private String getMimeType(File file) {String extension = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase();switch (extension) {case "mp4":case "mov":case "mkv":case "avi":case "wmv":case "m4v":case "mpg":case "vob":case "webm":case "ogv":case "3gp":case "flv":case "f4v":case "swf":return "video/" + extension;default:return "video/x-generic"; // 通用视频类型,可能不是最优解}}
删除
//子线程中使用
public void delFile(List<VideoInfoBean> list) {List<VideoInfoBean> files = list;for (VideoInfoBean appInfoBean : files) {File file = new File(appInfoBean.path);if (null != file) {file.delete();}}
}
音乐
先创建音乐文件的bean对象来承载数据
public class MusciInfoBean implements Serializable {public int id;//文件名public String name;//播放时长public String time;//文件路径public String path;//文件大小public long packageSize;//是否可选择public boolean isSelect;@Overridepublic String toString() {return super.toString();}
}
工具类
包名import android.media.MediaPlayer;
import java.io.IOException;public class MusicFileUtils {/*** 获取音频播放时长* @param path* @return*/public static String getPlayDuration(String path) {MediaPlayer player = new MediaPlayer();String duration="";try {//recordingFilePath()为音频文件的路径player.setDataSource(path);player.prepare();//获取音频的时间duration= timeParse(player.getDuration());} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}finally {player.release();}return duration;}/*** 获取音频播放时长* @param path* @return*/public static String getPlayDuration2(String path) {MediaPlayer player = new MediaPlayer();String duration="";try {//recordingFilePath()为音频文件的路径player.setDataSource(path);player.prepare();//获取音频的时间duration= timeParse2(player.getDuration());} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}finally {player.release();}return duration;}/**** @param duration 音乐时长* @return*/public static String timeParse(long duration) {String time = "" ;long minute = duration / 60000 ;long seconds = duration % 60000 ;long second = Math.round((float)seconds/1000) ;if( minute < 10 ){time += "0" ;}time += minute+"''" ;if( second < 10 ){time += "0" ;}time += second+"'" ;return time ;}/**** @param duration 音乐时长* @return*/public static String timeParse2(long duration) {String time = "" ;long minute = duration / 60000 ;long seconds = duration % 60000 ;long second = Math.round((float)seconds/1000) ;if( minute < 10 ){time += "0" ;}time += minute+"分" ;if( second < 10 ){time += "0" ;}time += second+"秒" ;return time ;}
}
获取音乐视频文件的对象列表
private List<MusciInfoBean> musciInfoBeans = new ArrayList<MusciInfoBean>();private void queryAllMusic(){Cursor cursor = activity.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,new String[] { MediaStore.Audio.Media._ID,MediaStore.Audio.Media.DISPLAY_NAME,MediaStore.Audio.Media.TITLE,MediaStore.Audio.Media.DURATION,MediaStore.Audio.Media.ARTIST,MediaStore.Audio.Media.ALBUM,MediaStore.Audio.Media.YEAR,MediaStore.Audio.Media.MIME_TYPE,MediaStore.Audio.Media.SIZE,MediaStore.Audio.Media.DATA },null,null,null);while (cursor.moveToNext()){//if (cursor.moveToFirst()) {int duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));
// String tilte = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME));String url = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID));File file=new File(url);if(null!=file){files.add(file);MusciInfoBean musciInfoBean = new MusciInfoBean();musciInfoBean.id=id;musciInfoBean.name = file.getName();musciInfoBean.packageSize = file.length();musciInfoBean.path = file.getPath();//musciInfoBean.time = MusicFileUtils.getPlayDuration(file.getPath());musciInfoBean.time=MusicFileUtils.timeParse(duration);musciInfoBeans.add(musciInfoBean);}}try {cursor.close();}catch (Exception e){e.printStackTrace();}}//按时间升序排列
Collections.sort(musciInfoBeans, ((o1, o2) -> o2.time.compareTo(o1.time)));
展示的就不说了,就把上面的信息展示出来就行
播放
public void play(MusciInfoBean musciInfoBean) {try {if (musciInfoBean.path == null){return;}String filePath = musciInfoBean.path;if (filePath.isEmpty()) return;File file = new File(filePath);if (!file.exists()) {// 文件不存在,可以在这里处理错误return;}// 获取文件 MIME 类型String mimeType = getMimeType(file);// 检查 MIME 类型是否为视频类型if (!isAudioType(mimeType)) {// 不是音乐文件,可以在这里处理错误return;}// 创建 UriUri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);// 创建 IntentIntent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(uri, mimeType);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);// 检查是否有应用可以处理此意图if (intent.resolveActivity(getPackageManager()) != null) {startActivity(intent);} else {Toast.makeText(this, "当前系统无可播放音乐的软件!", Toast.LENGTH_SHORT );}} catch (IllegalStateException e) {e.printStackTrace();}}private boolean isAudioType(String mimeType) {return mimeType.startsWith("audio/");}private String getMimeType(File file) {String extension = file.getName().substring(file.getName().lastIndexOf('.') + 1).toLowerCase();if ("mp3".equals(extension) || "wav".equals(extension) || "aac".equals(extension) ||"flac".equals(extension) || "ogg".equals(extension) || "m4a".equals(extension) ||"wma".equals(extension) || "aiff".equals(extension) || "amr".equals(extension)) {return "audio/" + extension;} else {return "audio/x-generic"; // 通用音频类型,可能不是最优解}}
删除
//子线程中使用
public void delFile(List<MusciInfoBean> list) {List<MusciInfoBean> files = list;for (MusciInfoBean appInfoBean : files) {File file = new File(appInfoBean.path);if (null != file) {file.delete();}}
}
文件
文件的含义就多了,像pdf,word,xml,json,txt,docx等等都可以算文件
先创建文件的bean对象来承载数据
/*** 文件 实体类* @property fileName String 文件名* @property isFile Boolean true---文件;false---文件夹* @property filePath String 文件路径* @property date String 格式化后的时间* @property originalDate String 原始时间(时间戳)* @property size String 格式化后的大小* @property originaSize Long 原始大小(byte)* @property folderLength Int 文件夹包含的文件个数* @property parent String? 父级所包含的选择文件个数(自定义文件获取不需要给该字段赋值)*/
//有些字段无实际意义,按需获取就行
data class ZFileBean(var fileName: String = "",var isFile: Boolean = true,var filePath: String = "",var date: String = "",var originalDate: String = "",var size: String = "",var originaSize: Long = 0L,var folderLength: Int = 0,var parent: String? = "",var folderName: String = "",var thumbPath: String = "",var fullPath: String = "",var imageCount: Int = 0,var isDelete:Boolean? = false, //是否选中删除var originalduration:Long =0, //原始视频的时长
) : Serializable ,Parcelable
获取文件对象列表
//这个方法其实可以获取到所有本地的文件,如视频、图片、音乐、压缩包、安装包等,只要传入不同的后缀数组就行val filefilterArray = arrayOf(DOC, DOCX, XLS, XLSX, PPT, PPTX, PDF, TXT, ZIP, JSON, XML) //还有补充的可以再加private fun getLocalData(filterArray: Array<String>): MutableList<ZFileBean> {val list = arrayListOf<ZFileBean>()var cursor: Cursor? = nulltry {val fileUri = MediaStore.Files.getContentUri("external")val projection = arrayOf(MediaStore.Files.FileColumns.DATA,MediaStore.Files.FileColumns.TITLE,MediaStore.Files.FileColumns.SIZE,MediaStore.Files.FileColumns.DATE_MODIFIED)val sb = StringBuilder()filterArray.forEach {if (it == filterArray.last()) {sb.append(MediaStore.Files.FileColumns.DATA).append(" LIKE '%.$it'")} else {sb.append(MediaStore.Files.FileColumns.DATA).append(" LIKE '%.$it' OR ")}}val selection = sb.toString()val sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIEDval resolver = getContext()?.contentResolvercursor = resolver?.query(fileUri, projection, selection, null, sortOrder)if (cursor?.moveToLast() == true) {do {val path =cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA))val size =cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns.SIZE))val date =cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATE_MODIFIED))val fileSize = ZFileOtherUtil.getFileSize(size)val lastModified = ZFileOtherUtil.getFormatFileDate(date * 1000)if (size > 0.0) {val name = path.substring(path.lastIndexOf("/") + 1, path.length)list.add(ZFileBean(name,true,path,lastModified,date.toString(),fileSize,size))}} while (cursor.moveToPrevious())}} catch (e: Exception) {ZFileLog.e("根据特定条件 获取文件 ERROR ...")e.printStackTrace()} finally {cursor?.close()return list}}
展示
展示没啥说的,顶多根据文件的后缀名显示不同的图片就行
点击打开
在res中添加一个xml的文件夹,在xml文件夹中创建dn_file_paths.xml文件
<?xml version="1.0" encoding="utf-8"?>
<paths><files-pathname="files_path"path="." /><cache-pathname="cache_path"path="." /><external-pathname="external_path"path="." /><external-files-pathname="external_files_path"path="." /><external-cache-pathname="external_cache_path"path="." /><external-media-pathname="external_media_path"path="." /><!-- 添加共享目录--><external-pathname="external_files"path="." />
</paths>
在AndroidManifast.xml中注册内容提供商
<applicationandroid:allowBackup="true"><providerandroid:name="androidx.core.content.FileProvider"android:authorities="包名.provider"android:exported="false"android:grantUriPermissions="true"tools:replace="android:authorities"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/dn_file_paths"/></provider></application>
打开方法
private fun openFile(filePath: String) {if (filePath.isEmpty()) returnval file = File(filePath)if (!file.exists()) {// 文件不存在,可以在这里处理错误return}val mimeType = getMimeType(file)val uri = FileProvider.getUriForFile(this, "${this.packageName}.provider", file)val intent = Intent(Intent.ACTION_VIEW).apply {setDataAndType(uri, mimeType)addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)}// 检查是否有应用可以处理此意图if (intent.resolveActivity(packageManager) != null) {startActivity(intent)} else {// 没有应用可以处理此文件类型// 可以提示用户安装支持的应用程序}}private fun getMimeType(file: File): String {val extension = file.extension.toLowerCase()return when (extension) {"doc", "docx" -> "application/msword""xls", "xlsx" -> "application/vnd.ms-excel""ppt", "pptx" -> "application/vnd.ms-powerpoint""pdf" -> "application/pdf""txt" -> "text/plain""json" -> "application/json"else -> "*/*" // 通用类型,可能不是最优解}}
删除
//子线程中使用
public void delFile(List<ZFileBean> list) {List<ZFileBean> files = list;for (ZFileBean appInfoBean : files) {File file = new File(appInfoBean.filePath);if (null != file) {file.delete();}}
}
相关文章:
安卓 文件管理相关功能记录
文件管理细分为图片、视频、音乐、文件四类 目录 权限 静态声明权限 动态检查和声明权限方法 如何开始上述动态申请的流程 提示 图片 获取图片文件的对象列表 展示 删除 视频 获取视频文件的对象列表 获取视频file列表 按日期装载视频文件列表 展示 播放 删除…...
GB28181系列三:GB28181流媒体服务器ZLMediaKit
我的音视频/流媒体开源项目(github) GB28181系列目录 目录 一、ZLMediaKit介绍 二、 ZLMediaKit安装、运行(Ubuntu) 1、安装 2、运行 3、配置 三、ZLMediaKit使用 一、ZLMediaKit介绍 ZLMediaKit是一个基于C11的高性能运营级流媒体服务框架,项目地址…...
ScottPlot学习的常用笔记
ScottPlot学习的常用笔记 写在前面版本的选择第一个障碍:版本问题。 ScottPlot4.0的官方网站与示例官方起始页cookbook5.0Demo4.1 demo以4.1为例,解压和运行如下: 下载源代码和编译先说结论: 写在前面 之前调研的TraceCompass&am…...
二、mapbox-gl实现白膜立体建筑
有时候我们只有二维的面数据,怎么实现类似高德地图中的白膜立体建筑呢?在mapbox-gl中很容易实现,具体如下。 要在Vue中结合Mapbox显示自定义的GeoJSON数据,并实现建筑物的白膜效果,我们需要执行以下步骤: …...
mybatisplus 分库查询
mybatisplus 分库查询 比如我们的项目有两个数据库 不同的表在不同的库 我们是可以使用mybatisplus来实现 首选引入pom <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><vers…...
计算属性 (vue3)
二 实例 1route.query.type...
RabbitMQ 安装、配置和使用介绍 使用前端js直接调用方式
1. 安装 RabbitMQ 1.1 安装 Erlang RabbitMQ 是基于 Erlang 语言开发的,因此首先需要安装 Erlang。 在 Ubuntu 上安装 Erlang: bash sudo apt-get update sudo apt-get install erlang 在 CentOS 上安装 Erlang: bash sudo yum insta…...
电脑显示器选购指南2024
选择显示器是五花八门的显示参数,如何选择,以下给出参数说明,及部分参考: 1. 尺寸和分辨率 尺寸(英寸) 根据使用距离和用途选择合适的屏幕尺寸: 21-24 英寸:适合小桌面空间、日常…...
vue2中如何实现自定义指令
实现自动聚焦功能 1.不用自定义指令 使用生命周期钩子mounted 2.使用自定义指令 1. 使用全局组件 首先在main.js中注册 然后在组件中直接使用v-指令名 2. 使用局部注册 局部注册和全局注册类似 如果很多组件需要自定义,建议使用自定义指令注册在全局...
QT从入门到精通(一)——Qlabel介绍与使用
1. QT介绍——代码测试 Qt 是一个跨平台的应用程序开发框架,广泛用于开发图形用户界面(GUI)应用程序,也支持非图形应用程序的开发。Qt 提供了一套工具和库,使得开发者能够高效地构建高性能、可移植的应用程序。以下是…...
【jpa】springboot使用jpa示例
目录 1. 请求示例2. pom依赖3. application.yaml4.controller5. service6. repository7. 实体8. 启动类 1. 请求示例 curl --location --request POST http://127.0.0.1:8080/user \ --header User-Agent: Apifox/1.0.0 (https://apifox.com) \ --header Content-Type: applic…...
Python Flask Web框架快速入门
Flask 入门Demo Flask 开发环境搭建,执行如下指令: pip install flask# 第一节: Flask 快速入门from flask import Flask app Flask(__name__)app.route(/flask) def hello_flask():return Hello Flaskapp.run()核心代码剖析: 从flask包导…...
ansible自动化运维(五)roles角色管理
目录 Roles角色管理 创建nginx的角色目录 定义任务配置文件: 定义jinjia2模板: 定义nginx yml文件 检查语法,执行命令 检验结果: Roles角色管理 角色(roles)是ansible自1.2版本开始引入的新特性&…...
前端学习一
一 进程与线程 线程是进程执行的最小单位,进程是系统分配任务的最小单位。 一个进程可执行最少一个线程。线程分为子线程和主线程。 主线程关闭则子线程关闭。 二 浏览器进程 浏览器是多进程多线程应用。 进程包括: 浏览器进程 负责程序交互渲染…...
【OSS】php使用oss存储
阿里云oss官方文档:文档 1、前期工作 创建阿里云账号,登录创建bucket,注意修改权限,要不然可能读取不到 申请accessKeyId和accessKeySecret accessKey 2、项目中安装OSS扩展 composer require aliyuncs/oss-sdk-php3、基础使…...
UE5 C+、C++、C# 构造方法区别示例
我们对比一下UE C、C 、C#的构造方法: 1. UE4 C例子: // 声明和构造合并在一起static ConstructorHelpers::FObjectFinder<UTexture2D> CrosshairTexObj(TEXT("/Game/Path"));// 使用加载的资源UTexture2D* Texture CrosshairTexObj.…...
leetcode-146.LRU缓存(易理解)
为了实现一个满足 LRU(最近最少使用)缓存约束的数据结构,我们需要在 (O(1)) 时间复杂度内完成 get 和 put 操作。这通常可以通过结合使用哈希表和双向链表来实现: 哈希表:用于在 (O(1)) 时间复杂度内实现对缓存中元素…...
JavaSe部分总结
我们先来了解一下Java语言,JavaSE是Java编程语言的标准版,主要是来学习Java的基本语法,书写方式,以及一些简单的逻辑循环和判断,包括一些关键字,特殊类(抽象类),特殊的方法(static修饰的方法,final修饰的方法)等等,最重要的是Java语言是比较C语言和C语言是比较简单的,Java是面向…...
iPhone批量删除照片的方法
对于每一个iPhone用户来说,照片管理是一项日常而重要的任务。随着时间的积累,无数的照片快速填满了我们的存储空间,从美丽的风景到重要的家庭聚会,每一张照片都记录着我们生活中的瞬间。然而,当存储空间即将耗尽时&…...
红日靶场vulnstack 7靶机的测试报告[细节](一)
目录 一、测试环境 1、系统环境 2、注意事项 3、使用工具/软件 二、测试目的 三、操作过程 1、信息搜集 2、Redis未授权访问漏洞获取web1靶机系统权限 3、获取docker靶机系统权限 ①Laravel框架漏洞利用getshell ②Laravel主机的提权&&docker容器逃逸 提权…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...
深度解析:etcd 在 Milvus 向量数据库中的关键作用
目录 🚀 深度解析:etcd 在 Milvus 向量数据库中的关键作用 💡 什么是 etcd? 🧠 Milvus 架构简介 📦 etcd 在 Milvus 中的核心作用 🔧 实际工作流程示意 ⚠️ 如果 etcd 出现问题会怎样&am…...
高抗扰度汽车光耦合器的特性
晶台光电推出的125℃光耦合器系列产品(包括KL357NU、KL3H7U和KL817U),专为高温环境下的汽车应用设计,具备以下核心优势和技术特点: 一、技术特性分析 高温稳定性 采用先进的LED技术和优化的IC设计,确保在…...
