安卓 文件管理相关功能记录
文件管理细分为图片、视频、音乐、文件四类
目录
权限
静态声明权限
动态检查和声明权限方法
如何开始上述动态申请的流程
提示
图片
获取图片文件的对象列表
展示
删除
视频
获取视频文件的对象列表
获取视频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容器逃逸 提权…...
雯雯的后宫-造相Z-Image-瑜伽女孩惊艳效果展示:新月式体式+柔光原木场景生成实录
雯雯的后宫-造相Z-Image-瑜伽女孩惊艳效果展示:新月式体式柔光原木场景生成实录 安全声明:本文仅展示AI图像生成技术效果,所有内容均基于技术演示目的,不涉及任何不当内容。 1. 效果惊艳开场:当瑜伽遇见AI艺术 今天要…...
魔兽争霸3现代化修复指南:三步让经典游戏在Windows 10/11完美运行
魔兽争霸3现代化修复指南:三步让经典游戏在Windows 10/11完美运行 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸3在现代电…...
探索NextDNS Config:优化你的DNS配置以提升网络性能
探索NextDNS Config:优化你的DNS配置以提升网络性能 是一个开源项目,旨在帮助用户轻松地管理并优化其设备上的NextDNS设置。该项目由Yokoffing开发,并提供了多种平台(包括路由器、Android和iOS)的配置文件,…...
Doorkeeper与Rails Engines集成终极指南:如何在大型项目中组织认证模块
Doorkeeper与Rails Engines集成终极指南:如何在大型项目中组织认证模块 【免费下载链接】doorkeeper Doorkeeper is an OAuth 2 provider for Ruby on Rails / Grape. 项目地址: https://gitcode.com/gh_mirrors/do/doorkeeper Doorkeeper是一个强大的OAuth …...
隐私优先方案:OpenClaw+Qwen3-14B镜像处理敏感数据的5层防护
隐私优先方案:OpenClawQwen3-14B镜像处理敏感数据的5层防护 1. 为什么需要本地化隐私方案 去年处理一批客户调研数据时,我犯过一个致命错误——把包含联系方式的原始表格上传到某公有云AI平台进行清洗。三天后,公司邮箱突然收到匿名勒索邮件…...
PyTorch 2.8镜像惊艳案例:碳排放数据→双碳目标达成路径视频推演
PyTorch 2.8镜像惊艳案例:碳排放数据→双碳目标达成路径视频推演 1. 效果惊艳开场 想象一下,只需输入简单的碳排放数据,就能自动生成一段专业级的双碳目标达成路径推演视频。这不是科幻场景,而是我们基于PyTorch 2.8镜像实现的真…...
PagerDuty与NodeJS集成:构建高效监控告警系统的实践指南
1. 为什么需要PagerDuty与NodeJS集成? 在当今的互联网服务架构中,系统的稳定性和可用性至关重要。想象一下,如果你的电商网站在凌晨3点突然宕机,而整个团队都在熟睡中,这会导致多少订单流失?这就是监控告警…...
AGV如何实现自主避障
下面按“传感器→建模→算法→安全机制→工程实现”的顺序,把AGV自主避障讲清楚。 一、整体架构概览 AGV要“自己绕开障碍”,至少要做三件事: 1)感知:知道“我在哪”“周围有什么”; 2)规划&…...
短视频 SEO 优化能给企业带来什么好处_短视频 SEO 如何优化视频标题和描述
短视频 SEO 优化能给企业带来什么好处_短视频 SEO 如何优化视频标题和描述 在当今数字化时代,短视频平台已经成为了企业营销和品牌推广的重要渠道。短视频的传播范围和影响力远不止于视频内容本身,背后的搜索引擎优化(SEO)策略同…...
0基础入门网络安全必练这两个靶场!挖漏洞必先从刷靶场开始
0基础入门网络安全必练这两个靶场!挖漏洞必先从刷靶场开始 第一「皮卡丘」 它是国内几个安全大佬专门给小白开发的中文靶场,界面非常简洁而且操作友好,真的也算是我刚入门时候的一个实战老师 和其他靶场不同,它既可以动手练习还…...
