Android ViewPager/PagerAdapter ImageView OutOfMemoryError

Posted

技术标签:

【中文标题】Android ViewPager/PagerAdapter ImageView OutOfMemoryError【英文标题】: 【发布时间】:2012-12-08 19:59:21 【问题描述】:

我在 *** 和其他地方看到过各种类似的问题,但这些解决方案似乎都没有解决我的问题,如下所示:

我有一个带有简单 PagerAdapter 的 ViewPager。每个“页面”都有一个与之关联的 XML 布局,其中至少包含一个 TextView、一个 ImageView,有时还包含另一个 TextView。与 ImageView 关联的图像通常在一个维度上为 1000px,在另一个维度上更小,但它们是 8 位索引 gif,因此它们都不会大于 20kB。我将它们设置为 Drawable,它们会适当地缩小以适应分配给它们的空间。

只有 6 页。当我启动活动时,它工作正常,我什至可以翻到最后一页,但是如果我一直向右翻,然后一直向左翻,我会得到一个 OutOfMemoryError。这对我来说没有意义,因为我认为 ViewPager 应该销毁超过 1 个屏幕以外的页面,并且我正在按照其他地方的建议处理 destroyItem()。

这是 PagerAdapter 的代码:

package doop.doop.dedoop;

import doop.doop.dedoop.R.layout;
import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

public class HelpPagerAdapter extends PagerAdapter
    private Context applicationContext;
    private Context activityContext;

    public HelpPagerAdapter(Context appContext, Context activContext)
        applicationContext = appContext;
        activityContext = activContext;
    

    public int getCount() 
        return 6;
    

    public Object instantiateItem(ViewGroup collection, int position) 
        LayoutInflater inflater = (LayoutInflater) collection.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        int resId = 0;
        int ivId = 0;
        int drawId = 0;
        switch (position) 
        case 0:
            resId = R.layout.help1;
            ivId = R.id.helpImage1;
            drawId = R.drawable.left_col_highlight;
            break;
        case 1:
            resId = R.layout.help2;
            ivId = R.id.helpImage2;
            drawId = R.drawable.right_col_highlight;
            break;
        case 2:
            resId = R.layout.help3;
            ivId = R.id.helpImage3;
            drawId = R.drawable.p2win;
            break;
        case 3:
            resId = R.layout.help4;
            ivId = R.id.helpImage4;
            drawId = R.drawable.first_move;
            break;
        case 4:
            resId = R.layout.help5;
            ivId = R.id.helpImage5;
            drawId = R.drawable.screen_bottom;
            break;
        case 5:
            resId = R.layout.help6;
            ivId = R.id.helpImage6;
            drawId = R.drawable.illegal_move;
            break;
        
        View view = inflater.inflate(resId, null);
        collection.addView(view, 0);

        if (ivId != 0 && drawId != 0) 
            ImageView iv = (ImageView) view.findViewById(ivId);
            iv.setImageDrawable(activityContext.getResources().getDrawable(drawId));
        

        return view;
    

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) 
        container.removeView((View)object);   
    

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) 
        return arg0 == ((View) arg1);
    


这是其中一种页面布局的示例:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        style="@style/helpPageLayout" >

    <TextView
        style="@style/helpText"
        android:text="@string/help2" />

    <ImageView android:id="@+id/helpImage2"
        style="@style/helpImage" />            

</LinearLayout>

这是整个活动所在的活动:

package doop.doop.dedoop;

import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewGroup;

public class Help extends Activity 

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_help);

        // Set up the ViewPager and its kin
        HelpPagerAdapter adapter = new HelpPagerAdapter(getApplicationContext(), this);
        ViewPager helpPager = (ViewPager) findViewById(R.id.helpPager);
        helpPager.setAdapter(adapter);      

    

    public void exitHelp(View view) 
        finish();
    


这是该 Activity 的布局:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mainHelp"
    android:orientation="vertical"
    android:layout_
    android:layout_>

    <android.support.v4.view.ViewPager android:id="@+id/helpPager"
        android:layout_
        android:layout_
        android:layout_weight="1" />

    <Button
        android:text="@string/helpBack"
        android:onClick="exitHelp"
        style="@style/mainMenuButtons" />

</LinearLayout>

最后,这是错误:

12-08 11:55:23.114: E/AndroidRuntime(9090): FATAL EXCEPTION: main
12-08 11:55:23.114: E/AndroidRuntime(9090): java.lang.OutOfMemoryError
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.nativeCreate(Native Method)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:618)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:593)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:445)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:775)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.content.res.Resources.loadDrawable(Resources.java:1968)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.content.res.Resources.getDrawable(Resources.java:677)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at doop.doop.dedoop.HelpPagerAdapter.instantiateItem(HelpPagerAdapter.java:69)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:801)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager.populate(ViewPager.java:962)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager.populate(ViewPager.java:881)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.support.v4.view.ViewPager$3.run(ViewPager.java:237)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.os.Handler.handleCallback(Handler.java:605)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.os.Handler.dispatchMessage(Handler.java:92)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.os.Looper.loop(Looper.java:137)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at android.app.ActivityThread.main(ActivityThread.java:4514)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at java.lang.reflect.Method.invokeNative(Native Method)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at java.lang.reflect.Method.invoke(Method.java:511)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
12-08 11:55:23.114: E/AndroidRuntime(9090):     at dalvik.system.NativeStart.main(Native Method)

我想可能值得注意的是,这仅发生在我目前正在测试的两台设备中的一台上(三星 Galaxy SIII,但不是 Kindle Fire HD 8.9)。

【问题讨论】:

同样的问题:在 Galaxy S2 上工作,在 Galaxy Tab 10 上内存不足。滚动到右边,好的,回到左边 OOM。 【参考方案1】:

您可以尝试以下几点:

使用 PNG 作为图像,尝试一些 png-crunch-app(或使用 photoshop > 导出到网络) 回收您的观点:

将您从容器中删除的视图保存在 ArrayList 中的 destroyItem() 中。在 instantiateItem() 期间,首先检查您的数组列表中是否有未使用的视图,如果有,请使用该视图。如果没有,请充气。这样,您只需增加 3 个视图,并回收其余视图。查看here 了解有关 ViewHolders 的更多信息,以防止调用 findViewById() 过多。

更高效地加载位图:

使用带有 inJustDecodeBounds = true 的 BitmapFactory.Options 来解码您的位图并检查位图的边界。 然后使用 inSampleSize 解码较小的版本。如果这与您相关,请查看here 了解更多信息。

主要项目

利用 pagerAdapter 的setPrimaryItem()。并在那里加载位图。 这样,您只加载当前可见项目的位图,从而减少内存负载。但是有点破坏了预加载上一页/下一页以获得更流畅的滚动和更好的用户体验的意义。

示例代码(未经测试!)

private ImageView primaryView;

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object)

    super.setPrimaryItem(container, position, object);
    if (this.primaryView == object)
    
        //this method might be called a lot, so don't do anything unless the primaryView changes!
        return ;
    
    if (this.primaryView != null)
    
        //recycle the previous bitmap to clear memory
        ((BitmapDrawable)this.primaryView.getDrawable()).getBitmap().recycle();
    
    //set new primary view
    this.primaryView = (ImageView) object;
    int resourceId = 0;//get image resource id here
    //load bitmap here efficiently
    this.primaryView.setImageResource(resourceId);

【讨论】:

我还要说... 1/ 运行 DDMS 并确保碎片被销毁。例如,也许他们持有对使他们不适合 gc 的上下文的硬引用。作为快速测试,您可以在 Pagelistener.settle 上发出 System.gc 并来回滚动前 2 页...在 logcat 下检查您是否没有泄漏内存...我很确定是这种情况... 【参考方案2】:
helpPager.setOffscreenPageLimit(MAX_PAGES);

当页面超出这个范围时它会销毁。

【讨论】:

根据文档,屏幕外页面限制的默认值为 1,所以我的印象是手动设置它不应该对内存有任何帮助。无论如何我都试过了,值是1,然后是0,都没有解决问题。很明显,这些页面,或者至少是 Drawable,没有被破坏。作为一个实验,我将它设置为 5,当我第一次使用 ViewPager 加载活动时,应用程序崩溃了。 也许它与 gif 有关,谷歌不鼓励使用它们。 嗯,它们最初是 PNG 文件,我也遇到了同样的问题。我将它们转换为索引 gif 以使它们更小,希望更小的文件可以减少内存负载。【参考方案3】:

在清单文件的应用程序标签中设置 android:largeHeap="true" 解决了我在 viewpager 中出现 outOfMemory 异常的问题。

【讨论】:

好吧...如果你有内存泄漏,大堆只会在 OOM 之前给你一些时间。我不会无缘无故地推荐使用 largeheap。请注意,它会影响您的应用程序速度,也会对整个系统产生不利影响。 是的,根据 rupps,这不是解决方案。 那么我应该用什么来解决这个问题呢? Jelle 提供了迄今为止最好的答案。

以上是关于Android ViewPager/PagerAdapter ImageView OutOfMemoryError的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本

Android逆向-Android基础逆向(2-2)

【Android笔记】android Toast

图解Android - Android核心机制

Android游戏开发大全的目录