Android 文件管理器 文件缩略图标显示流程

前言

    本篇文章是基于Android 11 文件管理器(com.android.documentsui)的源码,在实际项目中定位加载图片和视频文件显示缩略图的相关问题时,做的总结,文章中缩略图加载显示原理,查看过Android 10 和 12的源码,流程基本一致。

一、文件图标

     文管在手机系统中扮演一个管理,分类,操作内部存储中所有类型文件的角色,把存储中的图片,音频,视频,文档,下载apk等各种文件通过归类整理,然后呈现给用户。

      文档,音频,下载apk,其显示图标一般是对应的固定图标, 而图片和视频为了更好的区分其内容,展现给用户是缩略图, 如下图:

          

二.  显示原理

      2.1  文管中界面显示框架也是采用 Activity + Fragment 实现的, 具体的Activity为:com.android.documentsui.files.FilesActivity.java    Fragment为:com.android.documentsui.dirlist.DirectoryFragment.java

       文管中文件显示有两种视图:MODE_GRID(网格模式) 和  MODE_LIST(列表模式) 

       每次点击一个菜单或者新进入一个文件夹,都会创建一个新的Fragment,然后替换之前的Fragment,好了,我们来具体看看这个DirectoryFragment.java文件:

       整个界面显示是使用Recyclerview这个控件来完成的,核心代码如下:

./src/com/android/documentsui/dirlist/DirectoryFragment.java

@Override
  public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

       mRecView = (RecyclerView) view.findViewById(R.id.dir_list);

       //当视图回收的时候,取消加载缩略图的task
       mRecView.setRecyclerListener(
                new RecyclerListener() {
                    @Override
                    public void onViewRecycled(ViewHolder holder) {
                        cancelThumbnailTask(holder.itemView);
                    }
                });
       //设置Item动画
       mRecView.setItemAnimator(new DirectoryItemAnimator());

}


@Override
    public void onActivityCreated(Bundle savedInstanceState) {

        // 适配器实例化     
        mAdapter = new DirectoryAddonsAdapter(
                mAdapterEnv,
                new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper, mInjector.fileTypeLookup)
        );
        
        // 设置适配器
        mRecView.setAdapter(mAdapter);

        // 这里的mColumnCount是通过calculateColumnCount(mode)计算得来,显示有几列
        // MODE_LIST视图下只显示1列
        mLayout = new GridLayoutManager(getContext(), mColumnCount) {
            @Override
            public void onLayoutCompleted(RecyclerView.State state) {
                super.onLayoutCompleted(state);
                mFocusManager.onLayoutCompleted();
            }
        };
        
        //设置网格LayoutManager
        mRecView.setLayoutManager(mLayout);

}


     

     2.2 那数据信息是从哪里来的呢? 请留意DirectoryAddonsAdapter.java这个文件,我们知道:

      RecyclerView显示View的控件,Adapter从模型层获取数据,然后提供给RecyclerView显示,它是沟通的桥梁。

      Adapter的主要任务是:创建自定义的RecylerView.ViewHolder,然后将模型层的数据绑定到

ViewHolder上, 在实现上通常要复写以下三个方法:

  1. onCreateViewHolder(ViewGroup parent, int viewType)
 当需要新的ViewHolder来显示列表项时,会调用onCreateViewHolder方法去创建新的ViewHolder

  2. onBindViewHolder(CrimeHolder holder, int position)
 将数据绑定在ViewHolder上。

  3. getItemCount()
返回总共要显示的列表的数量

相关文件:
./src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java
./src/com/android/documentsui/dirlist/DocumentsAdapter.java
./src/com/android/documentsui/dirlist/DocumentHolder.java



// 继承DocumentsAdapter 
final class DirectoryAddonsAdapter extends DocumentsAdapter {}

public abstract class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder> {}

//元素载体
public abstract class DocumentHolder
        extends RecyclerView.ViewHolder implements View.OnKeyListener {
    
    
     /**
     * Binds the view to the given item data.
     * @param cursor
     * @param modelId
     * @param state
     */
    // 此抽象方法在子类中去覆写,不同类型的文件加载不同的缩略图
    public abstract void bind(Cursor cursor, String modelId);

}


 

2.3  与之相关的GridDocumentHolder.java类

相关文件
./src/com/android/documentsui/dirlist/GridDocumentHolder.java

// 这里是网格模式下各个文件缩略图容器
final class GridDocumentHolder extends DocumentHolder {

      
     @Override
    public void bind(Cursor cursor, String modelId) {
        assert(cursor != null);

        mModelId = modelId;

        mDoc.updateFromCursor(cursor, getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));

        mIconHelper.stopLoading(mIconThumb);

        mIconMimeLg.animate().cancel();
        mIconMimeLg.setAlpha(1f);
        mIconThumb.animate().cancel();
        mIconThumb.setAlpha(0f);
        
        //缩略图加载显示的代码
        mIconHelper.load(mDoc, mIconThumb, mIconMimeLg, mIconMimeSm);

    }
}

  


2.4 最终加载是在ThumbnailLoader.java 类中完成的

相关文件:
./src/com/android/documentsui/dirlist/IconHelper.java
./src/com/android/documentsui/ThumbnailLoader.java

 public void load(Uri uri, String mimeType, int docFlags, int docIcon, long docLastModified,
            ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) {

        if (showThumbnail) {
            loadedThumbnail =
                loadThumbnail(uri, docAuthority, docLastModified, iconThumb, iconMime);
        }

}



 private boolean loadThumbnail(Uri uri, String docAuthority, long docLastModified,
            ImageView iconThumb, ImageView iconMime) {

     
     // 通过 ThumbnailLoader 这个task 去创建缩略图
     final ThumbnailLoader task = new ThumbnailLoader(uri, iconThumb,
                        mCurrentSize, docLastModified,
                        bitmap -> {
                            if (bitmap != null) {
                                iconThumb.setImageBitmap(bitmap);
                                animator.accept(iconMime, iconThumb);
                            }
                        }, true /* addToCache */);

    ProviderExecutor.forAuthority(docAuthority).execute(task);

}


public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implements Preemptable {

         @Override
    protected Bitmap doInBackground(Uri... params) {

        //返回缩略图的Bitmap对象

    }

}

        当然里面还有个缩略图的缓存机制,如果此文件已经创建过缩略图,下次可以直接从ThumbnailCache中拿出来,具体实现请查看源码文件ThumbnailCache.java

三、流程图

          

我们来梳理一下流程图:

   


四. 小结:

   上述就是文件缩略图的显示流程,供大家参考!

1.  在实际项目中有时会遇到视频文件缩略图显示不出来的问题?

    可能原因:DocumentsContract.getDocumentThumbnail(wrap(client), mUri, mThumbSize,         mSignal)这个方法无法创建缩略图,返回为空。

    解决方法:如果为空,我们可以通过传入文件路径然后生成缩略图,如下:

    /**
     * 获取视频缩略图(获取第一帧)
     * @param filePath
     * @return bitmap
     */
    public Bitmap getVideoThumbnail(String filePath) {
        Bitmap bitmap = null;
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            retriever.setDataSource(filePath);
            bitmap = retriever.getFrameAtTime(TimeUnit.MILLISECONDS.toMicros(1));
        } catch(IllegalArgumentException e) {
            e.printStackTrace();
        } finally {
            try {
                retriever.release();
            }
            catch (RuntimeException e) {
                e.printStackTrace();
            }
        }
        return bitmap;
    }

2. 同一个视频文件,在不同文件夹显示的缩略图不一致的问题?

    有时候外置SD卡/U盘 和 手机文件夹中的同一个视频,发现缩略图显示不一致现象,可以试试如下这个方法:获取视频中间的一帧来创建缩略图


/**
     * 获取视频的缩略图
     * 先通过ThumbnailUtils来创建一个视频的缩略图,然后再利用ThumbnailUtils来生成指定大小的缩略图。
     * 如果想要的缩略图的宽和高都小于MICRO_KIND,则类型要使用MICRO_KIND作为kind的值,这样会节省内存。
     * @param videoPath 视频的路径
     * @param width 指定输出视频缩略图的宽度
     * @param height 指定输出视频缩略图的高度度
     * @param kind 参照MediaStore.Images(Video).Thumbnails类中的常量MINI_KIND和MICRO_KIND。
     * 其中,MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96
     * @return 指定大小的视频缩略图
     */
    public static Bitmap getVideoThumbnail(String videoPath, int width, int height,int kind) {
        Bitmap bitmap = null;
        // 获取视频的缩略图
        bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, kind); //調用ThumbnailUtils類的靜態方法createVideoThumbnail獲取視頻的截圖;
        if(bitmap!= null){
            bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height,
                    ThumbnailUtils.OPTIONS_RECYCLE_INPUT);//調用ThumbnailUtils類的靜態方法extractThumbnail將原圖片(即上方截取的圖片)轉化為指定大小;
        }
        return bitmap;
    }

    

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值