安卓 文件管理相关功能记录

文件管理细分为图片、视频、音乐、文件四类

目录

权限

静态声明权限

动态检查和声明权限方法

如何开始上述动态申请的流程

提示

图片

获取图片文件的对象列表

展示

删除

视频

获取视频文件的对象列表

获取视频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();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/491155.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

找出1000以内的所有回文数

找出1000以内的所有回文数 方法概述检查回文数的方法伪代码C代码实现代码解析运行结果在计算机科学中,回文数是一种具有对称性质的数,即从左向右读和从右向左读都是相同的。例如,121、1331、12321都是回文数。本文将利用数据结构、C语言和算法的知识来编写一个程序,找出100…

如何在NGINX中实现基于IP的访问控制(IP黑白名单)?

大家好&#xff0c;我是锋哥。今天分享关于【如何在NGINX中实现基于IP的访问控制&#xff08;IP黑白名单&#xff09;&#xff1f;】面试题。希望对大家有帮助&#xff1b; 如何在NGINX中实现基于IP的访问控制&#xff08;IP黑白名单&#xff09;&#xff1f; 1000道 互联网大…

数据结构day3作业

一、完整功能【顺序表】的创建 【seqList.h】 #ifndef __SEQLIST_H__ #define __SEQLIST_H__#include <stdio.h> #include <string.h> #include <stdlib.h>//宏定义&#xff0c;线性表的最大容量 #define MAX 30//类型重定义&#xff0c;表示要存放数据的类…

云计算HCIP-OpenStack02

书接上回&#xff1a; 云计算HCIP-OpenStack01-CSDN博客 7.OpenStack核心服务 7.1Horizon&#xff1a;界面管理服务 Horizon提供了OpenStack中基于web界面的管理控制页面&#xff0c;用户或者是管理员都需要通过该服务进行OpenStack的访问和控制 界面管理服务需要依赖于keyston…

【Linux】基础IO-----重定向与缓冲区

目录 一、文件描述符分配规则&#xff1a; 二、重定向&#xff1a; 1、本质&#xff08;原理&#xff09;&#xff1a; 2、dup2的使用&#xff1a; 3、添加重定向功能到shell&#xff1a; 4、stdout与stderr&#xff1a; 三、Linux下一切皆文件&#xff1a; 四、缓冲区&…

音频客观测评方法PESQ

一、简介 语音质量感知评估&#xff08;Perceptual Evaluation of Speech Quality&#xff09;是一系列的标准&#xff0c;包括一种用于自动评估电话系统用户所体验到的语音质量的测试方法。该标准于2001年被确定为ITU-T P.862建议书[1]。PESQ被电话制造商、网络设备供应商和电…

ubuntu下anconda装pytorch

1、禁用nouveau sudo vim /etc/modprobe.d/blacklist.conf 在文件最后部分插入以下两行内容 blacklist nouveau options nouveau modeset0 更新系统 sudo update-initramfs -u 重启系统 2、装nvidia驱动 卸载原来驱动 sudo apt-get remove nvidia-* &#xff08;若安装…

Hyperledger Fabric 2.x 环境搭建

Hyperledger Fabric 是一个开源的企业级许可分布式账本技术&#xff08;Distributed Ledger Technology&#xff0c;DLT&#xff09;平台&#xff0c;专为在企业环境中使用而设计&#xff0c;与其他流行的分布式账本或区块链平台相比&#xff0c;它有一些主要的区别。 环境准备…

c++中类的应用综合练习

整理思维导图 课上类实现> 、<、!、||、&#xff01;和后自增、前自减、后自减运算符的重载 代码部分&#xff1a; #include <iostream> using namespace std; class complex {int rel;int vir; public:complex(int rel,int vir):rel(rel),vir(vir){}complex(){}…

视频智能分析平台LiteAIServer未戴安全帽检测算法助力矿山安全:精准监督矿工佩戴安全帽情况

矿山作业环境复杂多变&#xff0c;安全隐患层出不穷。其中&#xff0c;矿工未佩戴安全帽这一行为&#xff0c;看似微不足道&#xff0c;实则潜藏着巨大的安全风险。一旦发生事故&#xff0c;未佩戴安全帽的矿工将极易受到重创&#xff0c;甚至危及生命。因此&#xff0c;确保每…

k8s服务搭建与实战案例

Kubernetes&#xff08;K8s&#xff09;作为一个开源的容器编排平台&#xff0c;广泛应用于现代的云原生应用架构中。以下是一些常见的 **Kubernetes 实战案例**&#xff0c;包括从基础部署到高级应用场景的使用。通过这些案例&#xff0c;可以更好地理解 K8s 的运作原理和最佳…

JAVA学习日记(二十六)网络编程

一、网络编程的概念 常见的软件架构&#xff1a; 二、网络编程三要素 IP&#xff1a;设备在网络中的地址&#xff0c;是唯一的标识 端口号&#xff1a;应用程序在设备中的唯一标识 协议&#xff1a;数据在网络中传输的规则&#xff0c;常见的协议有UDP、TCP、http、https、f…

vue 设置 VUE_APP_TITLE 打包部署后不生效

VUE_APP_TITLE 名门望族云科技有限公司网站 这里的 名门望族云科技有限公司网站 两边不能加 (单引号) 部署后,浏览器刷新网站根目录

黑马头条day01 微服务搭建

1.请求调用流程 如http://localhost:8803/static/js/2.0195d7180dc783c3fe99.js这种静态资源&#xff0c;采用http的发送到本地8803端口的静态资源请求&#xff0c;而nginx配置的监听8801、8802、8803&#xff0c;所以请求走到nginx&#xff0c;nginx的admin配置文件 upstream…

LabVIEW热电偶传感器虚拟仿真实验系统

在教学和科研领域&#xff0c;实验设备的更新和维护成本较高&#xff0c;尤其是在经济欠发达地区&#xff0c;设备的短缺和陈旧化严重影响了教学质量。基于LabVIEW的热电偶传感器虚拟仿真实验系统能够通过模拟实验环境&#xff0c;提供一个成本低廉且效果良好的教学和研究平台。…

Win11 如何真正获取 Trustedinstaller 权限(非修改所有者及权限)

在新的系统中又该如何获得 Trustedinstaller 权限呢&#xff1f; 准备工作 1、首先&#xff0c;我们需要下载并安装 Set-NtTokenPrivilege 命令所需模块&#xff0c;我们先在系统 C 盘根目录新建名为“token”的文件夹。 2、以管理员身份运行 Powershell&#xff0c;然后输入…

ensp 静态路由配置

A公司有广州总部、重庆分部和深圳分部3个办公地点&#xff0c;各分部与总部之间使用路由器互联。广州、重庆、深圳的路由器分别为R1、R2、R3&#xff0c;为路由器配置静态路由&#xff0c;使所有计算机能够互相访问&#xff0c;实训拓扑图如图所示 绘制拓扑图 给pc机配置ip地址…

C语言学习day18:字符串操作/ANSI编码/宽字节/消息框/软件/游戏编码/逆向分析中的编码

今天我们将学习字符串操作&#xff0c;为什么要着重来说这个呢&#xff1f;因为这是为我们之后window开发和api做准备。好的&#xff0c;我们现在正式开始&#xff1a; 字符串 字符串就是一串文字。 比如&#xff1a;"好好学习&#xff0c;天天向上"就是一个字符串…

【论文阅读笔记】One Diffusion to Generate Them All

One Diffusion to Generate Them All 介绍理解 引言二、相关工作三、方法预备知识训练推理实现细节训练细节 数据集构建实验分结论附录 介绍 Paper&#xff1a;https://arxiv.org/abs/2411.16318 Code&#xff1a;https://github.com/lehduong/onediffusion Authors&#xff1…

深入理解偏向锁、轻量级锁、重量级锁

一、对象结构和锁状态 synchronized关键字是java中的内置锁实现&#xff0c;内置锁实际上就是个任意对象&#xff0c;其内存结构如下图所示 其中&#xff0c;Mark Word字段在64位虚拟机下占64bit长度&#xff0c;其结构如下所示 可以看到Mark Word字段有个很重要的作用就是记录…