即使回收Bitmap,Android仍然出现了内存不足异常。

3

我已经阅读了许多关于这个主题的帖子,并尝试在我的代码中使用它们,但我仍然无法解决问题。

我正在尝试做的事情: 像Facebook或任何其他从服务器下载包括图像的供稿的应用程序一样,我也在尝试显示提要和图片在图像视图上。

现状: 我能够下载(JSON格式)包含图像URL的提要,并将其显示在ImageView上。 在模拟器中,我的最大堆大小为64MB。在前10条提要中,我消耗了约30MB的内存(不确定为什么,但这是我从Android Studio的Memory选项卡和即使在Android设备监视器中获得的结果)。 我在我的应用程序中有一个刷新按钮,可以删除之前填充的所有提要后重新加载相同的提要。我期望我将消耗相同的内存或更多一些。但与此相反,我的内存使用量增加到42MB。因此,如果我点击3到4次刷新按钮,就会导致OutOfMemory Exception。即使每次加载10条或50条提要,我都会遇到OutOfMemory Exception。

我知道Facebook、Instagram和许多类似的应用程序都在做同样的事情,但不确定他们如何实现代码来解决这个问题。

以下是填充提要的代码:

private void loadFeed(List<Feed> feedList)
{
    Log.v(Constant.TAG,"Loading Feed in social feed page");
    for(final Feed feed:feedList) {
        LinearLayout feedBox = new LinearLayout(this);
        feedBox.setOrientation(LinearLayout.VERTICAL);
        FrameLayout profileDetailContainer= new FrameLayout(this);
        LinearLayout profileDetailContainerParent=new LinearLayout(this);
        LinearLayout profileDetailContainerChild=new LinearLayout(this);
        profileDetailContainerChild.setOrientation(LinearLayout.VERTICAL);
        ImageView imgProfile= new ImageView(this);
        TextView txtDate= new TextView(this);
        TextView txtName= new TextView(this);
        ImageView imgProduct= new ImageView(this);
        txtName.setText(feed.getUserFullName());
        TextView txtDesciption= new TextView(this);
        txtName.setTypeface(null, Typeface.BOLD);
        if(feed.getDescription().length()>Constant.NUMBER_OF_DESCRIPTION_CHAR_SHOW_ON_RESULT)
        {
            txtDesciption.setText(feed.getDescription().substring(0,Constant.NUMBER_OF_DESCRIPTION_CHAR_SHOW_ON_RESULT)+"...");
        }
        else
            txtDesciption.setText(feed.getDescription());

        if(!IOUtil.fileExists(this,feed.getProductImageName())) {
            WebRequest request = new WebRequest();
            request.setUrl(Constant.ROOT_APPLICATION_URL_WITH_SEPARATOR + feed.getImgPath());
            request.setParam(feed.getId() + "");
            new ImageDownloadTask(this, true, feed.getProductImageName(), this).execute(request);
            Log.v(Constant.TAG,"URL:"+Constant.ROOT_APPLICATION_URL_WITH_SEPARATOR+feed.getImgPath());
            Picasso.with(getApplicationContext()).load(R.drawable.logo).into(imgProduct);
            feedBox.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ScreenUtility.alertException(v.getContext(),"Please wait untill product image loads");
                }
            });
            PixyfiSession.save(feed.getId() + "", imgProduct);
            PixyfiSession.save(feed.getId() + "_feed", feed);

        }
        else
        {
            ImageUtil.recycleIfPossible(imgProduct);
            try {
                imgProduct.setImageBitmap(ImageUtil.getLocalImage(feed.getProductImageName(),this));
                FeedboxOnClickListener feedboxOnClickListener = new FeedboxOnClickListener(feed);
                feedBox.setOnClickListener(feedboxOnClickListener);
            } catch (FileNotFoundException e) {
                Log.v(Constant.TAG,e.getMessage(),e);
            }

        }

        if(FacebookUtil.localProfilePicExists(feed.getUserName(),this))
        {
            Bitmap profileImage= FacebookUtil.getLocalProfilePicture(feed.getUserName(),this);
            imgProfile.setImageBitmap(profileImage);
        }
        else {
            FacebookUtil.loadProfilePicture(feed.getUserName(),this,this);
            PixyfiSession.save(feed.getUserName(),imgProfile);
            imgProfile.setImageResource(R.drawable.profile);
        }
        try {

            if(feed.getDate()==null) {
                txtDate.setText(new SimpleDateFormat("dd MMM yyyy").format(new SimpleDateFormat("yyyy-MM-dd").parse(feed.getFeedDate())));
            }
            else {
                txtDate.setText(new SimpleDateFormat("dd MMM yyyy").format(feed.getDate()));
            }
        } catch (ParseException e) {
            Log.e(Constant.TAG,e.getMessage(),e);
        }
        LinearLayout.LayoutParams layoutParam= new LinearLayout.LayoutParams(140,140);
        layoutParam.setMargins(5,5,0,0);
        imgProfile.setLayoutParams(layoutParam);

        layoutParam= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT,Gravity.TOP|Gravity.LEFT);
        layoutParam.setMargins(20,5,0,0);
        txtName.setLayoutParams(layoutParam);
        layoutParam= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT,Gravity.LEFT);
        layoutParam.setMargins(20,5,0,0);
        txtDate.setLayoutParams(layoutParam);

        profileDetailContainerParent.addView(imgProfile);
        profileDetailContainerChild.addView(txtName);
        profileDetailContainerChild.addView(txtDate);
        profileDetailContainerParent.addView(profileDetailContainerChild);
        feedBox.addView(profileDetailContainerParent);
        LinearLayout.LayoutParams feedLayoutParam=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT,Gravity.CENTER_VERTICAL);
        feedLayoutParam.setMargins(5,5,5,5);
        imgProduct.setLayoutParams(feedLayoutParam);
        txtDesciption.setLayoutParams(feedLayoutParam);
        feedBox.addView(txtDesciption);
        feedBox.addView(imgProduct);
        feedLayoutParam=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT,Gravity.CENTER_VERTICAL);
        feedLayoutParam.setMargins(0,5,0,5);
        feedBox.setLayoutParams(feedLayoutParam);

        feedBox.setBackgroundColor(Color.parseColor("#cccccc"));
        PixyfiSession.save(feed.getId()+"_feedbox",feedBox);
        imgProfile.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PixyfiSession.save(Constant.SELECTED_USER_ID,feed.getUserName());
                ScreenUtility.loadScreen(v.getContext(),UsersProfile.class,false);
            }
        });
        this.feedContainer.addView(feedBox);

    }
    if(feedList.size()==Constant.FEED_SIZE_IN_ONE_REQUEST)
    {
        Button moreFeed= new Button(this);
        moreFeed.setText("Load MOre Feed");
        moreFeed.setOnClickListener(new MoreFeedButtonListener(this));
        this.feedContainer.addView(moreFeed);
    }
    this.progressBar.setVisibility(View.INVISIBLE);
}

重新加载/刷新信息流
this.currentPage=1;
this.recycleImages();
this.feedContainer.removeAllViews();
PixyfiSession.save(Constant.CURRENT_FEED_PAGE,this.currentPage);
this.progressBar.setVisibility(View.VISIBLE);
loadFeed();

recycleImages方法:

private void recycleImages()
{
    for(int i=0;i<this.feedContainer.getChildCount();i++)
    {
        if(this.feedContainer.getChildAt(i) instanceof  ImageView)
        {
            ImageView view=(ImageView)this.feedContainer.getChildAt(i);
            ImageUtil.recycleIfPossible(view);
        }
    }
}

如果您需要代码的更多细节,请告诉我。 另外,在Android设备监视器中是否可以查看其他应用程序(例如Facebook)的内存使用情况? 更新 ImageUtil.getLocalImage方法
public static Bitmap getLocalImage(String imageName, Context context) throws FileNotFoundException
{
    Bitmap bitmap=null;
    InputStream is=null;
    if(IOUtil.fileExists(context,imageName)) {
        is = context.openFileInput(imageName);
        bitmap= BitmapFactory.decodeStream(is);
    }
    else {
        throw new FileNotFoundException("Image file doesn't exists");
    }
    return bitmap;
}

你应该膨胀布局,因为有太多的UI组件。这将减少视图泄漏的可能性。如果你能使用Recycler View,那将会更有帮助。 - Alok
我不明白你的意思。如果你的意思是动态添加视图,那么我正在这样做。你能否再解释一下? - Kumar Gaurav
通过填充布局,您应该在xml文件中定义您的布局,然后使用它。这样可以更容易地管理,并且RecyclerView将帮助您重复使用相同的视图,最终在加载提要时使用较少的视图。 - Alok
如果您不想使用XML,仍应该至少尝试使用RecyclerView,这是显示列表数据的最佳内存优化方式,它还会强制使用ViewHolder模式。 - Alok
1
不,我的意思是RecyclerView,它是ListView的替代品。请查看此链接:http://developer.android.com/reference/android/support/v7/widget/RecyclerView.html - Alok
显示剩余8条评论
6个回答

0

正如Saber Safavi所说,先做这个,然后再添加。

 defaultConfig {
    ....
    multiDexEnabled true
    ....
}

在你的

app/build.gradle

文件..


Multidex允许我编写超过65536个方法。我没有问题引用我的方法。如果我错了,请纠正我。 - Kumar Gaurav

0

看起来你在加载图片方面遇到了问题。请检查你的ImageUtils.getLocalImages,你是否将缩放后的图片解码并加载到内存中,还是将原始图片加载到内存中?

按照Android开发者文档,我认为这是在Android中高效加载图片的最佳教程。


尝试对图像进行采样,结果发现我的模拟器宽度为1080像素(我正在使用ImageView的全宽度),而我加载的图像最大只有1024像素。所以这也行不通。 - Kumar Gaurav
你能展示一下 ImageUtils.getLocalImages 方法的实现吗? - Tien

0
在Android中处理图像非常棘手。最好使用一个专门针对此情况和涉及的复杂性而构建的库来处理。 我更喜欢 UniversalImageLoader 或 Piccaso!

我也是出于同样的原因在使用Picasso。它也在上面的代码中。你觉得我使用Picasso的方式有什么问题吗? - Kumar Gaurav
嗨,Kumar,你为什么要使用一个名为 getLocalImage 的方法?据我了解,Picasso 负责处理磁盘图像。如果不是,请纠正我。谢谢。 - Ruchira Randana
Ruchira:我想你误解了我的意思。实际上,我对Android还很陌生,所以才请你纠正我。 - Kumar Gaurav
我觉得那可能是我犯的错误。我会尝试并告诉你结果。谢谢。 - Kumar Gaurav
好的,库马尔!祝你好运! - Ruchira Randana
尝试过了。它消耗的内存较少,但随着数据量的增加最终会抛出OOM异常。 - Kumar Gaurav

0

这是因为你在运行时内存中保存了太多的位图图像,尽量不要创建太多的图像并将它们置空。

移动设备通常具有受限的系统资源。Android 设备可以只有 16MB 的可用内存供单个应用使用。虚拟机兼容性提供了各种屏幕大小和密度所需的最小应用程序内存。应该对应用程序进行优化,使其在此最小内存限制下运行。但请记住,许多设备都配置了更高的限制。

我不会建议你在 Android 清单中使用 heapSize 标志。希望你不是在开发游戏 :), 如果需要的话,可以选择 Retrofit 或任何其他图像处理库。我建议你查看 Google 提供的 BitmapFun 示例。它可以在开发者门户网站上找到。

http://developer.android.com/training/displaying-bitmaps/index.html

干杯!!


0
我正在添加答案:
不要自己管理视图组件,而应始终优先使用listview或更好的recycler view。因为它可以帮助您避免视图泄漏,并提供对已创建的视图更好的重用。

0

我有同样的问题,在应用程序标签中添加此行到您的清单文件:

    <application
        android:largeHeap="true"
        .
        .
        .

不幸的是,这并不能解决问题,只会延迟它。你需要使用适当的小部件或视图类型,就像上面被接受的答案一样。 - Roisgoen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接