安卓 文件管理相关功能记录
文件管理细分为图片、视频、音乐、文件四类
目录
权限
静态声明权限
动态检查和声明权限方法
如何开始上述动态申请的流程
提示
图片
获取图片文件的对象列表
展示
删除
视频
获取视频文件的对象列表
获取视频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容器逃逸 提权…...

ubuntu+ros新手笔记(二):古月·ROS2入门21讲学习笔记
系统ubuntu22.04 ros2 humble 按照如下视频教程学习的:【古月居】古月ROS2入门21讲 | 带你认识一个全新的机器人操作系统 此处仅记录我报错的地方,以及相应的解决方案,没有出错的略过! 对应的古月居ROS2入门21讲源码下载地址&a…...

Harmonyos之深浅模式适配
Harmonyos之换肤功能 概述实现原理颜色适配颜色资源配置工具类编写界面代码编写适配效果 概述 深色模式(Dark Mode)又称之为暗色模式,是与日常应用使用过程中的浅色模式(Light Mode)相对应的一种UI主题。 换肤功能应…...

牛客网 SQL2查询多列
SQL2查询多列 select device_id,gender,age,university //查询哪些字段 from user_profile //从哪个表中查找 每日问题 C 中面向对象编程如何处理异常? 在C中,面向对象编程(OOP)处理异常主要通过异常处理机制来实现。C 提供了…...

Angular由一个bug说起之十二:网页页面持续占用CPU过高
随着网络日益发达,网页的内容也更加丰富,形式也更加多样化。而随之而来的性能问题也不容小觑。这篇文章我会根据我在实践中遇到的一个问题来总结,我在面对性能问题的一些解决步骤,希望能对大家有所启发。 查找问题原因 我接触的…...

【从零开始入门unity游戏开发之——C#篇05】转义字符、@处理多行文本或者不使用转义字符、随机数
文章目录 一、转义字符1、什么是转义字符?2、常见的转义字符3、总结 二、使用处理多行文本或者不使用转义字符1、多行字符串2、不使用转义字符 三、随机数1、Random.Next()生成随机整数示例:生成一个随机整数生成指定范围内的随机整数 2、Random.NextSin…...

我们来对接蓝凌OA --报文格式
题记 数智化办公专家、国家高新技术企业、知识管理国家标准制定者、信创供应商10强…等等,这些和咱们有关系吗!!不好意思,走错片场了,刚和项目经理在甲方那边吹牛B想想刚刚的大饼,看看支付宝余额ÿ…...

旅游系统旅游小程序PHP+Uniapp
旅游门票预订系统,支持景点门票、导游产品便捷预订、美食打卡、景点分享、旅游笔记分享等综合系统 更新日志 V1.3.0 1、修复富文本标签 2、新增景点入驻【高级版本】3、新增门票核销【高级版】4、新增门票端口【高级版】...

Pytest-Bdd-Playwright 系列教程(15):背景(Background)
Pytest-Bdd-Playwright 系列教程(15):背景(Background) 前言一、什么是背景(Background)二、特性文件三、测试脚本四、运行测试总结 前言 在测试的过程中,我们往往会遇到这样的问题&…...

ionic V6 安装ios所需
npm install capacitor/ios添加ios平台 ruby要求3.0以上 rvm use ruby-3.1.0 --default npx cap add ios打开xcode看看创建的项目 npx cap open ios没有capacitor指定的位置, 估计之前pod(cocoapods)安装搞得Ruby环境很乱了......cocoapods整的我麻了... App/App/capacitor…...

3d模型展示-初探
由于工作原因,近一年没怎么写代码,有朋友问你做过3D模型展示吗,之前都是做以vue为框架做定制业务,这次抽时间试试3d模型展示。 软件功能 使用ThreeJS框架实现加载GLB模型,并添加动画效果,实现3d展示模型。…...