recyclerview ImageViews使用过多的内存

发布于 2025-01-22 09:20:30 字数 11470 浏览 3 评论 0 原文

我有一个文件共享应用程序,该应用程序通过网络传输文件。

为了使用户正确选择文件,我列出了设备上可用的所有文件并使UX更好,我在OnBindViewHolder中获得了视频和图像的缩略图。由于设备上有任意数量的照片和视频,因此这会导致加载大量的缩略图,并且鉴于它的回收模型,我希望它不会使用太多的内存,但显然可以使用。我尝试在onviewRecycled中调用bitmap.recycle,因为某些视图也可能会反映缩略图。尝试使用Glide,但并没有超越Glide。(context).load(bitmap).into(holder.imageview)。

同样,当我完成活动时,一些位图可能会持续存在(在内存分析器中检查),

但是我想开始在活动仍在前景时减少内存使用情况。

我缺少什么?

编辑我还必须说,该活动正在使用Tablayout,其中有5个针对5种不同模拟型的选项卡,

因此,这是页面适配器


public class SectionsPagerAdapter extends FragmentStateAdapter {

    public static final int[]              TAB_TITLES = new int[]{R.string.Text_files, R.string.audio_files, R.string.image_files, R.string.video_files/*, R.string.unknown_files*/,R.string.apk_files};
    public static final int[]              SEARCH_TITLE = new int[]{R.string.search_res};
    private final boolean isSearching;
    private ArrayList<FileItemsModel> searchResModel;
    private ArrayList<FileItemsModel> txtModel;
    private ArrayList<FileItemsModel> audioModel;
    private ArrayList<FileItemsModel> videoModel;
    private ArrayList<FileItemsModel> imageModel;
    private ArrayList<FileItemsModel> apkModel;
    //private ArrayList<FileItemsModel> unknownModel;



    public SectionsPagerAdapter(FragmentActivity fm, ArrayList<FileItemsModel> txtModel, ArrayList<FileItemsModel> audioModel,
                                ArrayList<FileItemsModel> videoModel, ArrayList<FileItemsModel> imageModel, ArrayList<FileItemsModel> apkModel/*, ArrayList<FileItemsModel> unknownModel*/) {
        super(fm);
        this.apkModel   = apkModel;
        this.txtModel   = txtModel;
        this.audioModel = audioModel;
        this.videoModel = videoModel;
        this.imageModel = imageModel;
        //this.unknownModel = unknownModel;
        isSearching = false;
    }

    public SectionsPagerAdapter(FragmentActivity fm, ArrayList<FileItemsModel> searchResModel) {
        super(fm);
        isSearching = true;
        this.searchResModel = searchResModel;
    }

    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
        searchResModel = txtModel = audioModel = videoModel = imageModel = apkModel = null;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        if (!isSearching) {
            switch (position) {
                case 0:
                    return PlaceholderFragment.newInstance(txtModel);
                case 1:
                    return PlaceholderFragment.newInstance(audioModel);
                case 2:
                    return PlaceholderFragment.newInstance(imageModel);
                case 3:
                    return PlaceholderFragment.newInstance(videoModel);
                case 4:
                    return PlaceholderFragment.newInstance(apkModel);
                /*case 5:
                    return PlaceholderFragment.newInstance(unknownModel);*/
                default:
                    throw new RuntimeException("invalid tab index " + position);
            }
        }

        return PlaceholderFragment.newInstance(searchResModel);

    }

    @Override
    public int getItemCount() {
        if (!isSearching)
            return TAB_TITLES.length;

        return SEARCH_TITLE.length;
    }
}

片段,该片段容纳了RecyClerview

    public class PlaceholderFragment extends Fragment implements FileItemsAdapter.ItemClickListener{

    private static final String MODEL_KEY = "MODEL_KEY";
    private ArrayList<FileItemsModel> model;
    private FragmentFileSelectorBinding binding;
    private FileItemsAdapter adapter;
    private RecyclerView recyclerGrid;
    private FileSelectorActivity fileActivity;


    public static PlaceholderFragment newInstance(ArrayList<FileItemsModel> model) {
        PlaceholderFragment fragment = new PlaceholderFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList(MODEL_KEY, model);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fileActivity = (FileSelectorActivity) getActivity();
        if (getArguments() != null)
            model = getArguments().getParcelableArrayList(MODEL_KEY);
    }

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

        binding = FragmentFileSelectorBinding.inflate(inflater, container, false);
        View root = binding.getRoot();
        recyclerGrid =  binding.FilesGridList;
        recyclerGrid.setLayoutManager(new GridLayoutManager(getContext(),3));
        adapter = new FileItemsAdapter(model);
        adapter.setClickListener(this);
        recyclerGrid.setAdapter(adapter);

        return root;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
        model = null;
        fileActivity = null;
        recyclerGrid.removeAllViews();
        recyclerGrid = null;
        adapter.setClickListener(null);
        adapter = null;
    }

    @Override
    public void onItemClick(View view, int position, ArrayList<FileItemsModel> list) {
        if(fileActivity.ChosenFiles.contains(list.get(position).getPath()))
            fileActivity.ChosenFiles.remove(list.get(position).getPath());
        else
            fileActivity.ChosenFiles.add(list.get(position).getPath());

        view.setAlpha(view.getAlpha() == 1 ? 0.5f : 1);
        list.get(position).setIsSelected(view.getAlpha() != 1);
    }
}

My Adapter

    public class FileItemsAdapter extends RecyclerView.Adapter<FileSelectorViewHolder> {

    private static final String[] queryRes = new String[] { MediaStore.MediaColumns._ID };
    private static final int thumbType = MediaStore.Images.Thumbnails.MICRO_KIND;
    private static final Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    private static final Uri videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    private static final String queryFilter = MediaStore.MediaColumns.DATA + "=?";
    private final ArrayList<FileItemsModel> FileItemsModelArrayList;
    private ItemClickListener listener;
    public FileItemsAdapter(ArrayList<FileItemsModel> FileItemsModelArrayList)
    {
        super();
        this.FileItemsModelArrayList = FileItemsModelArrayList;
    }

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

    @NonNull
    @Override
    public FileSelectorViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View item = inflater.inflate(R.layout.file_item,parent,false);
        return new FileSelectorViewHolder(item, listener,FileItemsModelArrayList);
    }


    @Override
    public void onBindViewHolder(@NonNull FileSelectorViewHolder holder, int position) {

        //TODO: sometimes item is null.
         FileItemsModel item = FileItemsModelArrayList.get(position);
         if(item != null) {
             holder.linearLayout.setAlpha(item.getIsSelected() ? 0.5f : 1);
             holder.fileInfo.setText(item.getFileName());
             String minSdk = item.getMinSdk();
             if (minSdk != null)
                 holder.minSdk.setText(minSdk);

             setFileIcon(item, holder);
         }

        item = null;
    }


    @Override
    public void onViewRecycled(@NonNull FileSelectorViewHolder holder) {
        super.onViewRecycled(holder);
       // Glide.with(holder.itemView.getContext()).clear(holder.fileImg);
        if(holder.fileBitmap != null)
        {
            holder.fileBitmap.recycle();
            holder.fileBitmap = null;
        }

        holder.fileImg.setImageBitmap(null);
        holder.itemView.setOnClickListener(null);
        this.listener = null;
    }

    @Override
    public void onViewDetachedFromWindow(@NonNull FileSelectorViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        if(holder.fileBitmap != null)
        {
            holder.fileBitmap.recycle();
            holder.fileBitmap = null;
        }
        holder.fileImg.setImageBitmap(null);
        holder.itemView.setOnClickListener(null);
        this.listener = null;

    }

    public void setClickListener(@Nullable ItemClickListener itemClickListener) {
        this.listener = itemClickListener;
    }

    public interface ItemClickListener {
        void onItemClick(View view, int position, ArrayList<FileItemsModel> list);
    }


    private Bitmap getThumbnail(ContentResolver contentResolver,FileItemsModel item) {

      
        
        String mimeType = item.getMimeType();
        Cursor ca = contentResolver.query(mimeType.startsWith("video") ? videoUri : imageUri,queryRes, queryFilter, new String[] {item.getPath()}, null);


        if (ca != null && ca.moveToFirst()) {
            int id = ca.getInt(ca.getColumnIndex(MediaStore.MediaColumns._ID));
            ca.close();
            return  mimeType.startsWith("video") ? MediaStore.Video.Thumbnails.getThumbnail(contentResolver, id,thumbType , null )
                    : MediaStore.Images.Thumbnails.getThumbnail(contentResolver, id,thumbType , null );
        }

        ca.close();
        return null;
    }



    public void setFileIcon(FileItemsModel item, @NonNull FileSelectorViewHolder holder)
    {
        ContentResolver contentResolver = item.getContentResolver();
        if(contentResolver != null) {
            holder.fileBitmap = getThumbnail(contentResolver, item);

            if (holder.fileBitmap != null) {
                holder.fileImg.setImageBitmap(holder.fileBitmap);
                return;
            }
        }

        holder.fileImg.setImageDrawable(FileSelectorActivity.getFileDrawable(item.getMimeType()));
    }


}

My View Holderer


    public class FileSelectorViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    TextView fileInfo ;
    TextView minSdk;
    ImageView fileImg;
    LinearLayout linearLayout;
    Bitmap fileBitmap;
    FileItemsAdapter.ItemClickListener listener;
    private final ArrayList<FileItemsModel> FileItemsModelArrayList;

    public FileSelectorViewHolder(View item, FileItemsAdapter.ItemClickListener listener, ArrayList<FileItemsModel> FileItemsModelArrayList)
    {
        super(item);
        this.minSdk           = item.findViewById(R.id.minSdk);
        this.fileInfo       = item.findViewById(R.id.FileName);
        this.fileImg       = item.findViewById(R.id.FileImg);
        this.linearLayout = item.findViewById(R.id.FileContainer);
        item.setOnClickListener(this);
        this.listener = listener;
        this.FileItemsModelArrayList = FileItemsModelArrayList;

    }


    @Override
    public void onClick(View view) {
        if(listener != null) listener.onItemClick(view,getAdapterPosition(), FileItemsModelArrayList);

    }
}

I have a file sharing app, that transmits files over network.

In order to make user selects files properly, i list all the files available on the device and to make a better UX, i get thumbnails of videos and images in OnBindViewHolder. Since there are arbitrary number of photos and videos on a device, this results in loading huge number of thumbnails, and given its a recyclerview i expected that it won't use much memory, but apparently it does. I tried calling bitmap.recycle in onViewRecycled since some views might also have a refrence to the thumbnail. Tried using glide but didn't go beyond glide.with(context).load(bitmap).into(holder.imageview).

Also when i finish the activity, some bitmaps might persist (checked in memory profiler)

But i want to begin with reducing memory usage while activity is still in foreground.

I'm missing something?

Edit also i have to say that the Activity is using a tablayout, that have 5 tabs for 5 different MimeTypes

so here is the page adapter


public class SectionsPagerAdapter extends FragmentStateAdapter {

    public static final int[]              TAB_TITLES = new int[]{R.string.Text_files, R.string.audio_files, R.string.image_files, R.string.video_files/*, R.string.unknown_files*/,R.string.apk_files};
    public static final int[]              SEARCH_TITLE = new int[]{R.string.search_res};
    private final boolean isSearching;
    private ArrayList<FileItemsModel> searchResModel;
    private ArrayList<FileItemsModel> txtModel;
    private ArrayList<FileItemsModel> audioModel;
    private ArrayList<FileItemsModel> videoModel;
    private ArrayList<FileItemsModel> imageModel;
    private ArrayList<FileItemsModel> apkModel;
    //private ArrayList<FileItemsModel> unknownModel;



    public SectionsPagerAdapter(FragmentActivity fm, ArrayList<FileItemsModel> txtModel, ArrayList<FileItemsModel> audioModel,
                                ArrayList<FileItemsModel> videoModel, ArrayList<FileItemsModel> imageModel, ArrayList<FileItemsModel> apkModel/*, ArrayList<FileItemsModel> unknownModel*/) {
        super(fm);
        this.apkModel   = apkModel;
        this.txtModel   = txtModel;
        this.audioModel = audioModel;
        this.videoModel = videoModel;
        this.imageModel = imageModel;
        //this.unknownModel = unknownModel;
        isSearching = false;
    }

    public SectionsPagerAdapter(FragmentActivity fm, ArrayList<FileItemsModel> searchResModel) {
        super(fm);
        isSearching = true;
        this.searchResModel = searchResModel;
    }

    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onDetachedFromRecyclerView(recyclerView);
        searchResModel = txtModel = audioModel = videoModel = imageModel = apkModel = null;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        if (!isSearching) {
            switch (position) {
                case 0:
                    return PlaceholderFragment.newInstance(txtModel);
                case 1:
                    return PlaceholderFragment.newInstance(audioModel);
                case 2:
                    return PlaceholderFragment.newInstance(imageModel);
                case 3:
                    return PlaceholderFragment.newInstance(videoModel);
                case 4:
                    return PlaceholderFragment.newInstance(apkModel);
                /*case 5:
                    return PlaceholderFragment.newInstance(unknownModel);*/
                default:
                    throw new RuntimeException("invalid tab index " + position);
            }
        }

        return PlaceholderFragment.newInstance(searchResModel);

    }

    @Override
    public int getItemCount() {
        if (!isSearching)
            return TAB_TITLES.length;

        return SEARCH_TITLE.length;
    }
}

Fragment that holds the recyclerview

    public class PlaceholderFragment extends Fragment implements FileItemsAdapter.ItemClickListener{

    private static final String MODEL_KEY = "MODEL_KEY";
    private ArrayList<FileItemsModel> model;
    private FragmentFileSelectorBinding binding;
    private FileItemsAdapter adapter;
    private RecyclerView recyclerGrid;
    private FileSelectorActivity fileActivity;


    public static PlaceholderFragment newInstance(ArrayList<FileItemsModel> model) {
        PlaceholderFragment fragment = new PlaceholderFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelableArrayList(MODEL_KEY, model);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        fileActivity = (FileSelectorActivity) getActivity();
        if (getArguments() != null)
            model = getArguments().getParcelableArrayList(MODEL_KEY);
    }

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

        binding = FragmentFileSelectorBinding.inflate(inflater, container, false);
        View root = binding.getRoot();
        recyclerGrid =  binding.FilesGridList;
        recyclerGrid.setLayoutManager(new GridLayoutManager(getContext(),3));
        adapter = new FileItemsAdapter(model);
        adapter.setClickListener(this);
        recyclerGrid.setAdapter(adapter);

        return root;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
        model = null;
        fileActivity = null;
        recyclerGrid.removeAllViews();
        recyclerGrid = null;
        adapter.setClickListener(null);
        adapter = null;
    }

    @Override
    public void onItemClick(View view, int position, ArrayList<FileItemsModel> list) {
        if(fileActivity.ChosenFiles.contains(list.get(position).getPath()))
            fileActivity.ChosenFiles.remove(list.get(position).getPath());
        else
            fileActivity.ChosenFiles.add(list.get(position).getPath());

        view.setAlpha(view.getAlpha() == 1 ? 0.5f : 1);
        list.get(position).setIsSelected(view.getAlpha() != 1);
    }
}

My adapter

    public class FileItemsAdapter extends RecyclerView.Adapter<FileSelectorViewHolder> {

    private static final String[] queryRes = new String[] { MediaStore.MediaColumns._ID };
    private static final int thumbType = MediaStore.Images.Thumbnails.MICRO_KIND;
    private static final Uri imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    private static final Uri videoUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
    private static final String queryFilter = MediaStore.MediaColumns.DATA + "=?";
    private final ArrayList<FileItemsModel> FileItemsModelArrayList;
    private ItemClickListener listener;
    public FileItemsAdapter(ArrayList<FileItemsModel> FileItemsModelArrayList)
    {
        super();
        this.FileItemsModelArrayList = FileItemsModelArrayList;
    }

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

    @NonNull
    @Override
    public FileSelectorViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View item = inflater.inflate(R.layout.file_item,parent,false);
        return new FileSelectorViewHolder(item, listener,FileItemsModelArrayList);
    }


    @Override
    public void onBindViewHolder(@NonNull FileSelectorViewHolder holder, int position) {

        //TODO: sometimes item is null.
         FileItemsModel item = FileItemsModelArrayList.get(position);
         if(item != null) {
             holder.linearLayout.setAlpha(item.getIsSelected() ? 0.5f : 1);
             holder.fileInfo.setText(item.getFileName());
             String minSdk = item.getMinSdk();
             if (minSdk != null)
                 holder.minSdk.setText(minSdk);

             setFileIcon(item, holder);
         }

        item = null;
    }


    @Override
    public void onViewRecycled(@NonNull FileSelectorViewHolder holder) {
        super.onViewRecycled(holder);
       // Glide.with(holder.itemView.getContext()).clear(holder.fileImg);
        if(holder.fileBitmap != null)
        {
            holder.fileBitmap.recycle();
            holder.fileBitmap = null;
        }

        holder.fileImg.setImageBitmap(null);
        holder.itemView.setOnClickListener(null);
        this.listener = null;
    }

    @Override
    public void onViewDetachedFromWindow(@NonNull FileSelectorViewHolder holder) {
        super.onViewDetachedFromWindow(holder);
        if(holder.fileBitmap != null)
        {
            holder.fileBitmap.recycle();
            holder.fileBitmap = null;
        }
        holder.fileImg.setImageBitmap(null);
        holder.itemView.setOnClickListener(null);
        this.listener = null;

    }

    public void setClickListener(@Nullable ItemClickListener itemClickListener) {
        this.listener = itemClickListener;
    }

    public interface ItemClickListener {
        void onItemClick(View view, int position, ArrayList<FileItemsModel> list);
    }


    private Bitmap getThumbnail(ContentResolver contentResolver,FileItemsModel item) {

      
        
        String mimeType = item.getMimeType();
        Cursor ca = contentResolver.query(mimeType.startsWith("video") ? videoUri : imageUri,queryRes, queryFilter, new String[] {item.getPath()}, null);


        if (ca != null && ca.moveToFirst()) {
            int id = ca.getInt(ca.getColumnIndex(MediaStore.MediaColumns._ID));
            ca.close();
            return  mimeType.startsWith("video") ? MediaStore.Video.Thumbnails.getThumbnail(contentResolver, id,thumbType , null )
                    : MediaStore.Images.Thumbnails.getThumbnail(contentResolver, id,thumbType , null );
        }

        ca.close();
        return null;
    }



    public void setFileIcon(FileItemsModel item, @NonNull FileSelectorViewHolder holder)
    {
        ContentResolver contentResolver = item.getContentResolver();
        if(contentResolver != null) {
            holder.fileBitmap = getThumbnail(contentResolver, item);

            if (holder.fileBitmap != null) {
                holder.fileImg.setImageBitmap(holder.fileBitmap);
                return;
            }
        }

        holder.fileImg.setImageDrawable(FileSelectorActivity.getFileDrawable(item.getMimeType()));
    }


}

my ViewHolder


    public class FileSelectorViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    TextView fileInfo ;
    TextView minSdk;
    ImageView fileImg;
    LinearLayout linearLayout;
    Bitmap fileBitmap;
    FileItemsAdapter.ItemClickListener listener;
    private final ArrayList<FileItemsModel> FileItemsModelArrayList;

    public FileSelectorViewHolder(View item, FileItemsAdapter.ItemClickListener listener, ArrayList<FileItemsModel> FileItemsModelArrayList)
    {
        super(item);
        this.minSdk           = item.findViewById(R.id.minSdk);
        this.fileInfo       = item.findViewById(R.id.FileName);
        this.fileImg       = item.findViewById(R.id.FileImg);
        this.linearLayout = item.findViewById(R.id.FileContainer);
        item.setOnClickListener(this);
        this.listener = listener;
        this.FileItemsModelArrayList = FileItemsModelArrayList;

    }


    @Override
    public void onClick(View view) {
        if(listener != null) listener.onItemClick(view,getAdapterPosition(), FileItemsModelArrayList);

    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情话已封尘 2025-01-29 09:20:30

编辑:我想念阅读,您尝试了Glide库。但是您尚未尝试在通话中使用磁盘缓存策略。

类似:

Glide.with(fragment).load(url).diskCacheStrategy(DiskCacheStrategy.ALL).into(imageView);

您可以在此处阅读有关滑行缓存的信息:

https:/> https:/ /bumptech.github.io/glide/doc/caching.html#disk-cache-strategies

如果您希望它们在显示时立即加载:

preload多个带有滑行的图像

Edit: I miss read, you tried the Glide library. But you haven't tried using a disk cache strategy on your call.

Something like:

Glide.with(fragment).load(url).diskCacheStrategy(DiskCacheStrategy.ALL).into(imageView);

You can read about Glide caching here:

https://bumptech.github.io/glide/doc/caching.html#disk-cache-strategies

You can also preload the images into a cache if you want them to load instantly when you display them:

Preload multiple images with Glide

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文