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

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

目录

权限

静态声明权限

动态检查和声明权限方法

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

提示

图片

获取图片文件的对象列表

展示

删除

视频

获取视频文件的对象列表

获取视频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 将结果发送出来,之后在需要接收的地方接收就行

  @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        //将权限申请结果广播出去
        LiveDataBus.getInstance().with("requestCode").setValue(requestCode+","+resultCode);
    }

    @Override
    public 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>() {
                        @Override
                        public 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;
                        }

                        @Override
                        public 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);

    }

    //使用
    @Override
    public 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;
            }

            // 创建 Uri
            Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);

            // 创建 Intent
            Intent 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;

    @Override
    public 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;
            }

            // 创建 Uri
            Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);

            // 创建 Intent
            Intent 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? = null
        try {
            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_MODIFIED
            val resolver = getContext()?.contentResolver
            cursor = 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-path
        name="files_path"
        path="." />

    <cache-path
        name="cache_path"
        path="." />

    <external-path
        name="external_path"
        path="." />

    <external-files-path
        name="external_files_path"
        path="." />

    <external-cache-path
        name="external_cache_path"
        path="." />

    <external-media-path
        name="external_media_path"
        path="." />
    <!--    添加共享目录-->
    <external-path
        name="external_files"
        path="." />
</paths>

在AndroidManifast.xml中注册内容提供商

<application
        android:allowBackup="true">

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="包名.provider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/dn_file_paths"/>
        </provider>

    </application>

打开方法

private fun openFile(filePath: String) {
        if (filePath.isEmpty()) return
        val 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();
         }
     }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Springmvc初学

什么是springmvc springmvc框架它是spring框架的一个分支。它是按照mvc架构思想设计的一款框架。springmvc的主要作用: 接收浏览器的请求数据&#xff0c;对数据进行处理&#xff0c;然后返回页面进行显示。 为什么要学习springmvc 如何使用springmvc&#xff1f; 1.创建maven…

知从科技总经理受邀参加上海临港新片区商会“湖畔TECS”技术分享沙龙(第五期)

11月26日&#xff0c;上海知从科技有限公司创始人陈荣波先生受邀出席临港新片区商会 “湖畔TECS”技术分享沙龙&#xff08;第五期&#xff09;活动&#xff0c;并在活动上为参会嘉宾们做了主题分享。本次活动由临港新片区商会主办&#xff0c;智能网联汽车创新联盟协办&#x…

Vue中纯前端实现导出简单Excel表格的功能

Vue 前端Excel导出 Vue中纯前端导出简单Excel表格的方法(使用vue-json-excel插件) 前言 在许多的后台系统中少不了导出Excel表格的功能&#xff0c;在项目中纯前端使用vue-json-excel插件来实现简单Excel表格的导出功能。 使用方法 1、安装依赖 npm install vue-json-exc…

QT6 Socket通讯封装(TCP/UDP)

为大家分享一下最近封装的以太网socket通讯接口 效果演示 如图&#xff0c;界面还没优化&#xff0c;后续更新 废话不多说直接上教程 添加库 如果为qmake项目中&#xff0c;在.pro文件添加 QT network QT core gui QT networkgreaterThan(QT_MAJOR_VERS…

ip_done

文章目录 路由结论 IP分片 数据链路层重谈Mac地址MAC帧报头局域网的通信原理MSS&#xff0c;以及MAC帧对上层的影响ARP协议 1.公司是不是这样呢? 类似的要给运营商交钱&#xff0c;构建公司的子网&#xff0c;具有公司级别的入口路由器 2&#xff0e;为什么要这样呢?? IP地…

LearnOpenGL学习(高级OpenGL -> 高级GLSL,几何着色器)

完整代码见&#xff1a;zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 高级GLSL 内建变量 顶点着色器 gl_PointSoze : float 输出变量&#xff0c;用于控制渲染 GL_POINTS 型图元时&#xff0c;点的大小。可用于粒子系统。将其设置为 gl_Position.z 时&#xff0c;可以使点…

vscode设置终端代理

转载请标明出处&#xff1a;小帆的帆的博客 设置终端代理 修改项目的.vscode/settings.json {"terminal.integrated.env.windows": {"http_proxy": "http://127.0.0.1:7890","https_proxy": "http://127.0.0.1:7890"}, }…

计算机视觉中的边缘检测算法

摘要&#xff1a; 本文全面深入地探讨了计算机视觉中的边缘检测算法。首先阐述了边缘检测的重要性及其在计算机视觉领域的基础地位&#xff0c;随后详细介绍了经典的边缘检测算法&#xff0c;包括基于梯度的 Sobel 算子算法、Canny 边缘检测算法等&#xff0c;深入剖析了它们的…

ComfyUI 与 Stable Diffusion WebUI 的优缺点比较

ComfyUI与Stable Diffusion WebUI都是AI绘画领域比较知名两款产品&#xff0c;两者存在诸多差异&#xff0c;本篇就带你熟悉二者的优劣&#xff0c;方便自己做出决策。 界面与操作 ComfyUI&#xff1a;界面简洁直观&#xff0c;通过节点和连线的方式构建工作流&#xff0c;用…

2024年12月16日Github流行趋势

项目名称&#xff1a;PDFMathTranslate 项目维护者&#xff1a;Byaidu reycn hellofinch Wybxc YadominJinta项目介绍&#xff1a;基于 AI 完整保留排版的 PDF 文档全文双语翻译&#xff0c;支持 Google/DeepL/Ollama/OpenAI 等服务&#xff0c;提供 CLI/GUI/Docker。项目star数…

CDGA|“数据池塘资源”理论的出现对数据治理有怎样的影响?

“数据池塘资源”这一理论实践&#xff0c;可以理解为将数据集视为一个池塘&#xff0c;其中蕴含着丰富的信息和资源&#xff0c;有待于人们去挖掘和利用。这一理论实践对数据管理、分析和应用等领域可能会产生一系列深远的影响。以下是对其可能影响的详细分析&#xff1a; 一、…

linux学习笔记02 linux中的基础设置(修改主机名、ip、防火墙、网络配置管理)

目录 修改主机名 ​编辑 修改ip地址 防火墙 关闭networkmanage 修改主机名 查看主机名 hostnamectl status 修改主机名 vim /etc/hostname 修改ip地址 vim /etc/sysconfig/network-scripts/ifcfg-ens33 输入这个命令后对照以下文件修改 TYPE"Ethernet" PROXY_M…

用户发送请求后服务端i/o工作过程

华子目录 服务端i/o介绍磁盘i/o机械磁盘的寻道时间、旋转延迟和数据传输时间常见的机械磁盘平均寻道时间值常见磁盘的平均延迟时间每秒最大IOPS的计算方法 网络i/o网络I/O处理过程磁盘和网络i/o 一次完整的请求在内部的执行过程 服务端i/o介绍 i/o在计算机中指Input/Output&am…

240004基于Jamva+ssm+maven+mysql的房屋租赁系统的设计与实现

基于ssmmavenmysql的房屋租赁系统的设计与实现 1.项目描述2.运行环境3.项目截图4.源码获取 1.项目描述 该项目在原有的基础上进行了优化&#xff0c;包括新增了注册功能&#xff0c;房屋模糊查询功能&#xff0c;管理员和用户信息管理等功能&#xff0c;以及对网站界面进行了优…

MinerU(2):GPU加速

目录 遗留问题&#xff1a;ubuntu使用特定conda环境CUDA加速速度对比 解析效果公式解析表格解析实验结论 遗留问题&#xff1a;ubuntu使用特定conda环境 发现在vscode中能查看到版本&#xff0c; 但是到虚拟机&#xff0c;同样的目录下查不到 可能是vscode能自己切换Python环…

C# 生成随机数的方法

C# 提供了一种强大而方便的工具类 Random &#xff0c;用于生成随机数。这里将分类讨论如何通过 C# 实现随机数生成&#xff0c;以及应用于实际情况中的一些具体方案。 一、Random 类概述 Random 类表示一个伪随机数生成器&#xff0c;用于生成满足随机性统计要求的数字序列。…

wazuh-modules-sca-scan

sca模块主函数wm_sca_main -> wm_sca_start 检查policy文件中的每一个项目wm_sca_check_policy static int wm_sca_check_policy(const cJSON * const policy, const cJSON * const checks, OSHash *global_check_list) {if(!policy) {return 1;}const cJSON * const id c…

SpringCloud微服务实战系列:03spring-cloud-gateway业务网关灰度发布

目录 spring-cloud-gateway 和zuul spring webflux 和 spring mvc spring-cloud-gateway 的两种模式 spring-cloud-gateway server 模式下配置说明 grayLb://system-server 灰度发布代码实现 spring-cloud-gateway 和zuul zuul 是spring全家桶的第一代网关组件&#x…

Arm Cortex-M处理器对比表

Arm Cortex-M处理器对比表 当前MCU处理器上主要流行RISC-V和ARM处理器&#xff0c;其他的内核相对比较少&#xff1b;在这两种内核中&#xff0c;又以Arm Cortex-M生态环境相对健全&#xff0c;大部分的厂家都在使用ARM的处理器。本文主要介绍Arm Cortex-M各个不同系列的参数对…

如何实现规范化LabVIEW编程

规范编写LabVIEW程序的目的是提高代码的可读性、可维护性、可扩展性&#xff0c;并确保团队成员能够高效地理解和修改代码。以下是一些关键建议&#xff0c;帮助您编写更专业的LabVIEW代码&#xff0c;并确保它易于后续的升级和维护&#xff1a; ​ 1. 合理的项目结构 目录结构…