You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 11及以上机型WhatsApp状态加载异常,求适配Model与Adapter

问题根源分析

你的核心错误在于Android 11+(API 30+)不能通过File类直接处理SAF返回的content://类型Uri,new File(uri)完全无法正确解析这个Uri,导致后续的file.isDirectory()和listFiles()都不会执行,最终列表为空。必须使用DocumentFile来遍历SAF授权的目录。


1. StatusModel(数据模型)

存储SAF文件Uri、文件名、文件类型等核心信息:

public class StatusModel {
    private Uri fileUri;
    private String fileName;
    private boolean isVideo;

    public StatusModel(Uri fileUri, String fileName) {
        this.fileUri = fileUri;
        this.fileName = fileName;
        // 根据后缀判断是否为视频文件
        this.isVideo = fileName != null && (fileName.endsWith(".mp4") || fileName.endsWith(".avi") || fileName.endsWith(".mov"));
    }

    public Uri getFileUri() {
        return fileUri;
    }

    public String getFileName() {
        return fileName;
    }

    public boolean isVideo() {
        return isVideo;
    }
}

2. RecentAdapter(RecyclerView适配器)

使用Glide加载SAF Uri对应的媒体文件,包含基础点击事件:

public class RecentAdapter extends RecyclerView.Adapter<RecentAdapter.StatusViewHolder> {
    private Context context;
    private List<StatusModel> statusList;
    private OnItemActionListener actionListener;

    // 定义事件回调接口
    public interface OnItemActionListener {
        void onItemClick(StatusModel model);
        void onSaveClick(StatusModel model);
    }

    public RecentAdapter(Context context, List<StatusModel> statusList, OnItemActionListener actionListener) {
        this.context = context;
        this.statusList = statusList;
        this.actionListener = actionListener;
    }

    @NonNull
    @Override
    public StatusViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_status, parent, false);
        return new StatusViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull StatusViewHolder holder, int position) {
        StatusModel model = statusList.get(position);
        
        // 加载媒体内容
        if (model.isVideo()) {
            // 加载视频封面
            Glide.with(context)
                 .asBitmap()
                 .load(model.getFileUri())
                 .into(holder.statusImageView);
            holder.videoTag.setVisibility(View.VISIBLE);
        } else {
            // 加载图片
            Glide.with(context)
                 .load(model.getFileUri())
                 .into(holder.statusImageView);
            holder.videoTag.setVisibility(View.GONE);
        }

        // 绑定点击事件
        holder.itemView.setOnClickListener(v -> actionListener.onItemClick(model));
        holder.saveBtn.setOnClickListener(v -> actionListener.onSaveClick(model));
    }

    @Override
    public int getItemCount() {
        return statusList.size();
    }

    // ViewHolder定义
    public static class StatusViewHolder extends RecyclerView.ViewHolder {
        ImageView statusImageView;
        ImageView videoTag;
        Button saveBtn;

        public StatusViewHolder(@NonNull View itemView) {
            super(itemView);
            statusImageView = itemView.findViewById(R.id.iv_status);
            videoTag = itemView.findViewById(R.id.iv_video_tag);
            saveBtn = itemView.findViewById(R.id.btn_save);
        }
    }
}

对应的item_status.xml布局示例:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="4dp">

    <ImageView
        android:id="@+id/iv_status"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop"/>

    <ImageView
        android:id="@+id/iv_video_tag"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentBottom="true"
        android:layout_margin="8dp"
        android:src="@drawable/ic_video_play"/>

    <Button
        android:id="@+id/btn_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:layout_margin="8dp"
        android:text="保存"/>
</RelativeLayout>

3. 修正后的RecyclerView数据加载逻辑

替换你原有的Android 11+加载代码,使用DocumentFile遍历并排序文件:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    SharedPreferences sharedPreferences = getContext().getSharedPreferences("app", MODE_PRIVATE);
    String uriStr = sharedPreferences.getString("uri", "");
    
    // 校验Uri有效性
    if (TextUtils.isEmpty(uriStr)) {
        emptyLay.setVisibility(View.VISIBLE);
        return;
    }

    Uri treeUri = Uri.parse(uriStr);
    DocumentFile rootDir = DocumentFile.fromTreeUri(getContext(), treeUri);
    if (rootDir == null || !rootDir.exists() || !rootDir.isDirectory()) {
        emptyLay.setVisibility(View.VISIBLE);
        return;
    }

    f = new ArrayList<>();
    DocumentFile[] files = rootDir.listFiles();
    
    if (files != null) {
        // 转换为List并按修改时间倒序排序
        List<DocumentFile> fileList = Arrays.asList(files);
        Collections.sort(fileList, (file1, file2) -> 
            Long.compare(file2.lastModified(), file1.lastModified())
        );

        // 过滤有效文件
        for (DocumentFile file : fileList) {
            String fileName = file.getName();
            if (file.isFile() && fileName != null && !fileName.equals(".nomedia")) {
                f.add(new StatusModel(file.getUri(), fileName));
            }
        }
    }
}

关键注意事项

  • 异步处理:目录遍历和排序要放在子线程执行,避免阻塞UI线程,推荐用Coroutine或AsyncTask替代Handler.postDelayed。
  • 权限持久化:确保已通过takePersistableUriPermission获取持久化读权限,否则重启APP后权限会失效。
  • Glide兼容性:使用最新版Glide以支持SAF Uri加载,若仍有问题可添加自定义Uri解析配置。

内容的提问来源于stack exchange,提问作者Hamza khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 21:55:03