0

最初にいくつかの裏話。私のアプリの主要なビューの3つが拡張する基本的なビューがあります。子ビューは空で、アナログおよびデジタルです。これらの子ビューをグリッドビュー(2x3)に配置し、グリッドビューをスライディングドローに配置します。このスライディングドロワーは私のアプリの核心です。絶対に必要です。スライドドロワーはすべてのアクティビティに含まれている必要があるため、アクティビティが変更されると、アプリケーションに状態を保存し、新しいアクティビティが読み込まれたときに状態を取得します。

アプリが開くと、gridviewは6つの空のビューを作成し、それらをアダプターに追加します。これで、すべてのビューが空の間、アプリは問題なく動作します。アクティビティをさまよって、アプリが持つ他のすべての機能を実行できます。そして、同じ活動を続けている間、私は自分の心のコンテンツにアナログとデジタルのビューを作成することができます。それらは移動、削除、およびすべての機能を適切に実行します。しかし、別のアクティビティに切り替えて、グリッドビューにアナログビューまたはデジタルビューが1つでもあるとすぐに、アプリがを介してクラッシュしOutOfMemoryError: bitmap size exceeds VM Budgetます。

アナログビューとデジタルビューの両方で、2つのビットマップが作成されます。1つはビューの背景であり、もう1つはビューのユニークな外観であり、ほとんど変更されないため、ビットマップとして適しています。両方のビットマップはかなり小さいです(私のテストEvoでは221x221ピクセル)。そのため、活動の変化に応じて適切にリサイクルしていないと思いました。そこで私は戻って、すべてがクリーンアップされていることを確認し、各ビューを完全に破壊する方法を作成しました。すべての変数はnullに設定され、アクティビティが一時停止するたびにすべてのビットマップがリサイクルされます。(注:ロガーを使用して、onPauseが実際に呼び出されていることとdestroyメソッドが呼び出されていることを確認しました。)

今-数日後-私はまだこのメモリエラーがスローされている理由を理解できません。私はDDMSとMemoryTrackerを表示するのに説明のつかない時間を費やしましたが、これはおそらくこれまでで最も役に立たないことです。私はDDMSに完全にうんざりしています、私は私に何か有用なことを言うために愚かなことを得ることができません。

さて、質問です。プロセス(私のアプリ)の割り当ての完全なリストを取得し、印刷/表示/ファイルに保存するなどの方法(メソッド/システムコールなど)はありますか?

編集1:これはFalmarriへの応答です。少し多めに投稿しているかもしれませんが、お詫び申し上げます。もっと具体的なことを知りたいのなら、私が喜んでお手伝いします。私のコードを破る理由はありません。

クリップはBaseViewからのものです:

public abstract class GaugeBase extends View implements BroadcastListener {
    protected static final String TAG = "GaugeBase";
    // =======================================
    // --- Declarations 
    /** Animation dynamics. */
    protected float mTarget = 0, position = 0, velocity = 0.0f, acceleration = 0.0f;
    protected long lastMoveTime = -1L;

    /** Background objects. */
    protected Bitmap mBackground;
    protected Bitmap mFaceTexture;
    protected float borderSize = 0.02f;

    /** Face objects. */
    protected Paint mRimShadowPaint, mTitlePaint, mFacePaint, mRimPaint, mRimBorderPaint;
    protected Path mTitlePath;

    /** Bounding rects. */
    protected static RectF mRimRect, mFaceRect;

    /** Text tools. */
    protected static Typeface mTypeface;

    /** The preferred size of the widget. */
    private static final int mPreferredSize = 300;

    /** The Broadcaster the gauge is registered to. */
    protected SensorBroadcaster mCaster;

    /** Is the view instantiated? */
    private boolean isDestroyed = true;
    // ---
    // =======================================

    public GaugeBase(Context context) {
        super(context);
        mCaster = ((AppionApplication)getContext().getApplicationContext())
            .getSensorBroadcaster(AppionApplication.TEST_SENSOR);
        lastMoveTime = System.currentTimeMillis();
        setTarget(mCaster.getReading());
    }

    @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); }
    @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); }
    @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { regenerate(); } 
    @Override public void onBroadcastReceived() { setTarget(mCaster.getReading()); }

    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int chosenWidth = chooseDimension(widthMode, widthSize);
        int chosenHeight = chooseDimension(heightMode, heightSize);
        int chosenDimension = Math.min(chosenWidth, chosenHeight);
        setMeasuredDimension(chosenDimension, chosenDimension);
    }
    @Override protected void onDraw(Canvas canvas) {
        if (isDestroyed) return;
        if (mBackground == null) regenerate(); 
        canvas.drawBitmap(mBackground, 0, 0, null);
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        canvas.scale((float)getWidth(), (float)getWidth());
        drawForeground(canvas); canvas.restore(); animate();
    }

    public HashMap<String, Object> onSavePersistentState() {
        HashMap<String, Object> mState = new HashMap<String, Object>();
        mState.put("sensor_broadcaster", mCaster.getBroadcasterName());
        mState.put("type", this.getClass().getSimpleName());
        return mState;
    }

    public void onRestorePersistentState(HashMap<String, Object> state) {
        mCaster = ((AppionApplication)getContext().getApplicationContext())
            .getSensorBroadcaster((String)state.get("sensor_broadcaster"));
    }

    private final void setTarget(float target) { mTarget = target; animate(); }

    private static final int chooseDimension(int mode, int size) {
        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) return size;
        else return mPreferredSize;
    }

    private final void animate() {
        if (! (Math.abs(position - mTarget) > 0.01f)) return;
        if (lastMoveTime != -1L) {
            long currentTime = System.currentTimeMillis();
            float delta = (currentTime - lastMoveTime) / 1000.0f;
            float direction = Math.signum(velocity);
            if (Math.abs(velocity) < 90.0f) acceleration = 10.0f * (mTarget - position);
            else acceleration = 0.0f;
            position += velocity * delta;
            velocity += acceleration * delta;
            if ((mTarget - position) * direction < 0.01f * direction) {
                position = mTarget;
                velocity = 0.0f;
                acceleration = 0.0f;
                lastMoveTime = -1L;
            } else lastMoveTime = System.currentTimeMillis();               
            invalidate();
        } else {
            lastMoveTime = System.currentTimeMillis();
            animate();
        }
    }

    public void preInit() {
        mTypeface = Typeface.createFromAsset(getContext().getAssets(),
                "fonts/SFDigitalReadout-Heavy.ttf");
        mFaceTexture = BitmapFactory.decodeResource(getContext().getResources(),
                R.drawable.gauge_face);
        BitmapShader shader = new BitmapShader(mFaceTexture, 
                Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);

        Matrix matrix = new Matrix();
        mRimRect = new RectF(0.05f, 0.05f, 0.95f, 0.95f);
        mFaceRect = new RectF(mRimRect.left + borderSize, mRimRect.top + borderSize,
                             mRimRect.right - borderSize, mRimRect.bottom - borderSize);


        mFacePaint = new Paint();
        mFacePaint.setFilterBitmap(true);
        matrix.setScale(1.0f / mFaceTexture.getWidth(), 1.0f / mFaceTexture.getHeight());
        shader.setLocalMatrix(matrix);
        mFacePaint.setStyle(Paint.Style.FILL);
        mFacePaint.setShader(shader);

        mRimShadowPaint = new Paint();
        mRimShadowPaint.setShader(new RadialGradient(0.5f, 0.5f, mFaceRect.width() / 2.0f,
                new int[] { 0x00000000, 0x00000500, 0x50000500 },
                new float[] { 0.96f, 0.96f, 0.99f },
                Shader.TileMode.MIRROR));
        mRimShadowPaint.setStyle(Paint.Style.FILL);

        mRimPaint = new Paint();
        mRimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mRimPaint.setShader(new LinearGradient(0.4f, 0.6f, 0.6f, 1.0f,
                Color.rgb(0xff0, 0xf5, 0xf0), Color.rgb(0x30, 0x31, 0x30),
                Shader.TileMode.CLAMP));

        mRimBorderPaint = new Paint();
        mRimBorderPaint.setAntiAlias(true);
        mRimBorderPaint.setStyle(Paint.Style.STROKE);
        mRimBorderPaint.setColor(Color.argb(0x4f, 0x33, 0x36, 0x33));
        mRimBorderPaint.setStrokeWidth(0.005f);

        mTitlePaint = new Paint();
        mTitlePaint.setColor(0xff000000);
        mTitlePaint.setAntiAlias(true);
        mTitlePaint.setTypeface(mTypeface);
        mTitlePaint.setTextAlign(Paint.Align.CENTER);
        mTitlePaint.setTextSize(0.2f);
        mTitlePaint.setTextScaleX(0.8f);        

        // Now we prepare the gauge
        init();
        isDestroyed = false;
    }

    /** Update the gauge independent static buffer cache for the background. */
    private void regenerate() {
        if (isDestroyed) return;
        if(mBackground != null) { mBackground.recycle(); mBackground = null; } 
        // Our new drawing area
        mBackground = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas backCanvas = new Canvas(mBackground);
        float scale = (float)getWidth();
        backCanvas.scale(scale, scale);
        drawRim(backCanvas);
        drawFace(backCanvas);
        drawTitle(backCanvas);
        if (!(this instanceof EmptySpace)) { mCaster.getGroup().draw(backCanvas); }
        regenerateBackground(backCanvas);
    }

    /** Prepare the view to be cleaned up. This is called to prevent memory leaks. */
    public void destroy() { 
        isDestroyed = true;
        if (mFaceTexture != null) { mFaceTexture.recycle(); mBackground = null; }
        if (mBackground != null) { mBackground.recycle(); mBackground = null; }
        mRimShadowPaint = null;
        mRimShadowPaint = null;
        mFacePaint = null;
        mRimPaint = null;
        mRimBorderPaint = null;
        mTitlePath = null;
        mRimRect = null; mFaceRect = null;
        mTypeface = null;
        destroyDrawingCache();
    }

    /**
     * Create a bitmap of the gauge. The bitmap is to scale. 
     * @return The bitmap of the gauge.
     */
    int tobitmap = 0;
    public Bitmap toBitmap() {
        Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas();
        canvas.setBitmap(b);
        draw(canvas);
        return b;
    }

    /** Update the gauge dependent static buffer cache for the background. */
    protected abstract void regenerateBackground(Canvas canvas);
    /** Initializes all of the objects the gauge widget will need before use. */
    protected abstract void init();
    /** This is called when drawing the background. Draws the bordered edge of the gauge. */
    protected abstract void drawRim(Canvas canvas);
    /** This is called when drawing the background. Draws the face of the gauge. */
    protected abstract void drawFace(Canvas canvas);
    /** This is called when drawing the background. Draws the title to the gauge. */
    protected abstract void drawTitle(Canvas canvas);
    /**
     *  This is called when drawing the foreground. The foreground includes items like the 
     *  scale of an analog gauge, or the text of a digital gauge. Also any other necessary
     *  items that need drawing go here. Note: drawForeground is called quickly, repeatedly, 
     *  make it run fast and clean.
     */
    protected abstract void drawForeground(Canvas canvas);
}

これはデジタルビューからのものです:(それはより小さく、それでもエラーを引き起こすため)

public class DigitalGauge extends GaugeBase {
    // ================================
    // --- Drawing tools
    private RectF lcdRect;
    private Paint lcdPaint, detailPaint;
    private Path facePath, borderPath;
    // ---
    // ================================
    public DigitalGauge(Context context) {
        super(context);
    }

    @Override protected void regenerateBackground(Canvas canvas) { }
    @Override protected void init() {
        lcdPaint = new Paint();
        lcdPaint.setColor(0xff000000);
        lcdPaint.setAntiAlias(true);
        lcdPaint.setStrokeWidth(0.005f);
        lcdPaint.setTextSize(0.4f);
        lcdPaint.setTypeface(mTypeface);
        lcdPaint.setTextAlign(Paint.Align.CENTER);

        detailPaint = new Paint();
        detailPaint.setColor(0xff000000);
        detailPaint.setTextSize(0.2f);
        detailPaint.setStrokeWidth(0.005f);
        detailPaint.setAntiAlias(true);
        detailPaint.setTypeface(mTypeface);
        detailPaint.setTextScaleX(0.8f);
        detailPaint.setTextAlign(Paint.Align.CENTER);

        facePath = new Path();
        facePath.moveTo(0.12f, 0.0f);
        facePath.lineTo(0.88f, 0.0f);
        facePath.arcTo(new RectF(), 0, 90);
        // TODO Make the trapazoidal look of the digital gauge


        lcdRect = new RectF(mFaceRect.left + borderSize, mFaceRect.top + borderSize,
                mFaceRect.right - borderSize, mFaceRect.top - borderSize - lcdPaint.getTextSize());
    }

    @Override protected void drawRim(Canvas canvas) {
        canvas.drawRect(mRimRect, mRimPaint);
        canvas.drawRect(mRimRect, mRimBorderPaint);
    }

    @Override protected void drawFace(Canvas canvas) {
        canvas.drawRect(mFaceRect, mFacePaint);
        canvas.drawRect(mFaceRect, mRimBorderPaint);
    }

    @Override protected void drawTitle(Canvas canvas) {
        canvas.drawText(mCaster.getBroadcasterSerial(), mFaceRect.left - 0.1f,
                mFaceRect.top + 0.1f, mTitlePaint);
    }
    @Override protected void drawForeground(Canvas canvas) {
        String display = "000000" + String.valueOf(Math.ceil(position));
        String read = display.substring(display.length()-8, display.length() - 2);
        canvas.drawText(read, 0.5f, lcdRect.top + lcdPaint.getTextSize() / 2, lcdPaint);
        /**canvas.drawText(mContext.getResources().getStringArray(R.array.pressureTypes)[measurement],
            0.5f, lcdRect.top + lcdPaint.getTextSize() , detailPaint);*/
    }
}

アプリケーションを通過する状態については、ビューのタイプとビューが表すキャスターの文字列名をハッシュマップに入れます。そのハッシュマップをグリッドビューに渡します。グリッドビューは、6つのマップすべてを、グリッドビュー内のビューの位置を表す配列に配置します。次に、その配列はアプリケーションに保持され、必要に応じて取得されます。

これがグリッドビューです。考えれば考えるほど、このクラスは問題があるのではないかと思います。

public class Workbench extends GridView {
    /** Our debugging tag */
    private static final String TAG = "Workbench";
    /** Name of the Workbench. */
    private String mId = "-1";
    /** The title of the Workbench. */
    private String mTitle = "Workbench";
    /** The list of Widgets that will be handled by the bench */
    private GaugeBase[] mContent = new GaugeBase[6];
    /** The current selection from the bench */
    private int mSelection = -1;

    /** When a GaugeBase is moves we want to remove from the adapter. Now we won't lose it.*/
    private GaugeBase mHeldGaugeBase = null;
    private Bitmap mHold = null;
    private boolean mIsHolding = false;
    private float x = -1000f, y = -1000f; // Where the held bitmap should be
    private Bitmap trash;
    private RectF trashBox;

    // The touch listener we will use if we need to move a widget around
    private OnTouchListener mWidgetExchanger = new OnTouchListener() {
        @Override public boolean onTouch(View v, MotionEvent e) {
            int w = getWidth(); int h = getHeight(); 
            float xx = e.getX(); float yy = e.getY();
            switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN: // Fall through
            case MotionEvent.ACTION_MOVE: 
                if (mIsHolding) {
                    x = e.getX() - mHold.getWidth()/2; y = e.getY() - mHold.getHeight()/2;
                    postInvalidate(); break;
                }
            case MotionEvent.ACTION_UP: 
                if (mIsHolding) {
                    if (trashBox.contains(xx, yy)) removeGaugeBase(mSelection);
                    else {
                        if ((xx < w / 2) && (yy < h /3)) makeSwitch(0);
                        else if ((xx > w / 2) && (yy < h /3)) makeSwitch(1);
                        else if ((xx < w / 2) && (yy > h /3) && (yy < h * .666)) makeSwitch(2);
                        else if ((xx > w / 2) && (yy > h /3) && (yy < h * .666)) makeSwitch(3);
                        else if ((xx < w / 2) && (yy > h *.666)) makeSwitch(4);
                        else if ((xx > w / 2) && (yy > h *.666)) makeSwitch(5);
                    }
                    mSelection = -1;
                    //mHeldGaugeBase.destroy(); mHeldGaugeBase = null;
                    mHold.recycle(); mHold = null;
                    trash.recycle(); trash = null;
                    mIsHolding = false;
                    setOnTouchListener(null);
                    x = -1000f; y = -1000f;
                    ((AppionApplication)getContext().getApplicationContext()).vibrate(200); update();
                }
                break;
            }
            return true;
        }
    };

    public Workbench(Context context) { this(context, null); }
    public Workbench(Context context, AttributeSet attrs) { this(context, attrs, 0); }
    public Workbench(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        for (int i = 0; i < mContent.length; i++) {
            mContent[i] = new EmptySpace(getContext());
        }
        setAdapter(new BenchAdapter());
        this.setOnItemClickListener(new OnItemClickListener() {
            @Override public void onItemClick(AdapterView<?> arg0, View view, final int pos, long arg3) {
                if (mContent[pos] instanceof EmptySpace) {
                    CharSequence[] items = {"Analog", "Digital"};
                    AlertDialog.Builder adb = new AlertDialog.Builder(getContext());
                        adb.setTitle("Add a widget?")
                            .setItems(items, new DialogInterface.OnClickListener () {
                                @Override public void onClick(DialogInterface arg0, int position) {
                                    mContent[pos].destroy();
                                    mContent[pos] = null;
                                    SensorBroadcaster s = ((AppionApplication)getContext().getApplicationContext()).
                                        getSensorBroadcaster(AppionApplication.TEST_SENSOR);
                                    switch (position) {
                                    case 0: // Add an Analog GaugeBase to the Workbench
                                        mContent[pos] = new AnalogGauge(getContext());
                                        // TODO: Option to link to a manager
                                        break;
                                    case 1: // Add a digital GaugeBase to the Workbench
                                        mContent[pos] = new DigitalGauge(getContext());
                                        // TODO: Option to link to a manager
                                        break;
                                    } mContent[pos].preInit();
                                    update();
                                }
                            });
                        adb.show();
                } //else new GaugeBaseDialog(getContext(), Workbench.this, (GaugeBase)view, pos).show();
            }
        });
        setOnItemLongClickListener(new OnItemLongClickListener() {
            @Override public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
                    int pos, long arg3) {
                mSelection = pos;
                mHold = mContent[pos].toBitmap();
                mHeldGaugeBase = mContent[pos];
                mHeldGaugeBase.destroy();
                trash = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getContext().getResources(),
                        R.drawable.trash), getWidth() / 10, getHeight() / 10, true);
                trashBox = new RectF(getWidth() / 2 - trash.getWidth()/2, getHeight() - trash.getHeight(),
                        getWidth() /2 + trash.getWidth() /2, getHeight());
                mContent[pos] = new EmptySpace(getContext());
                update();
                mIsHolding = true;
                setOnTouchListener(mWidgetExchanger);
                ((AppionApplication)getContext().getApplicationContext()).vibrate(300);
                return false;
            }
        });
    }

    /**
     * Perform a switch in within the bench. Exchange on slot with another.
     * @param slot The slot of the widgets list that we are switching to.
     */
    public void makeSwitch(int slot) {
        if (mSelection == -1) return;
        Log.i(TAG, "Performing a Widget switch");
        mContent[mSelection].destroy();
        mContent[mSelection] = mContent[slot];
        mContent[slot] = mHeldGaugeBase;
        mContent[slot].preInit();
        mContent[slot].invalidate();
        Log.d(TAG, " mSelection = " + mContent[mSelection] + " slot = " +mContent[slot]);
        update();                                                               
    }
4

3 に答える 3

1

これは @mah への返信のようなものですが、コメントするには長すぎます。

常にシステム ページ サイズの倍数で実行されます。

これは必ずしも真実ではありません。特にアンドロイドアプリの場合。さまざまなセマンティクスを持つさまざまなメモリ アロケータがあります。ただし、 NDK (C++) とは対照的に、JAVAについて話していると仮定すると、透過性はさらに低くなります。Java の仮想マシン (または dalvik) は、ほぼ確実にマシンの起動時にメモリを過剰に割り当て、アプリが少量のメモリを要求すると、そのプールから割り当てます。

プールのメモリが不足すると、OS から別のブロックを割り当ててプールに追加し、そこからブロックを返します。

ただし、たとえばビットマップに必要な LARGE メモリ ブロックを要求すると、JVM (または dalvik マシン) はmmapメモリの一部をマップするシステムのメソッドを使用する可能性が高くなります。正確な状況に応じて、プライベートな匿名マップを実行し、そのセクションへのアクセスを提供することができます. または、基本的にディスク上の内容のメモリ ビューであるメモリにファイルをマップすることもできます。これはおそらく、dalvic が大きなビットマップ割り当てを処理する方法です。

各ビューを完全に破壊するメソッドを作成しました

まず、Java ではそれを直接制御することはできません。オブジェクトを null に設定しても削除されません。そのオブジェクトへの参照が 1 つしかないと仮定しても、ガベージ コレクターがオブジェクトのデータをクリーンアップするまで待つ必要があります。


あなたの問題が何であるかを伝えることは本当に不可能であり、実際にはヒントですらありません. 私が本当に言えることは、ビットマップ用のスペースを実際よりも多くの場所に割り当てている可能性があるということです。または、クリーンアップされていない場所への参照を保持しています。

状態をアプリケーションに保存し、新しいアクティビティがロードされたときにそれを取得するだけです。

これはかなり漠然としていますが、まず、これで何をしているのかを見てみましょう。たとえば、ビットマップを解析可能なオブジェクトとして渡す場合、必要に応じておそらく 3 ~ 4 倍のスペースを割り当てます。解析可能なインターフェイスを介して大きなカスタム オブジェクトとビットマップを送信すると、非常にコストがかかります。

いくつかのことの1つを提案します。ビットマップを遅延ロードすることもできます。つまり、どこにも保管しないでください。必要なときだけ引き上げてください。これは、コンパイラの裏をかいていると思う場合があるため、解決策になる可能性があります。しかし、効率的なメモリ使用に関しては、コンパイラがあなたよりも賢いことを保証します。

--

または、反対のことを試して、アプリケーションのロード時にのみビットマップをロードし、表示するたびに新しいビットマップなどを作成していないことを確認してください。そうすれば、メモリ内に一度だけ存在し、実際に大きすぎる場合は、早い段階で既知の場所でクラッシュします。

--

最後に、解決策が本当に見つからず、Java プロセスが本当にメモリ不足になっていると本当に思っている場合は、NDK でビットマップ ビューを処理する部分を書き直すことができます。そうすれば、明示的に行わない限り、ガベージコレクションや再作成は行われません。

--

それでは質問です。プロセス(アプリ)の割り当ての完全なリストを取得し、印刷/表示/ファイルに保存/などする方法(メソッド/システムコールなど)はありますか?

きっとあるよ。しかし、これは大きな問題ですが、決して簡単なことではありません。おそらく、システムの glibc (具体的には malloc 関数) を、誰がメモリを要求したかを追跡するバージョンに置き換える必要があります。ただし、これでも Java 仮想マシンによって難読化されます。


簡単に言えば、コードの一部、特にビューとビットマップを操作および保存する部分を投稿してください。

更新

あなたのコードをちらりと見るだけでregenerate()、特にこれが原因で、どのくらいの頻度で呼び出されているかを確認できます。

mBackground = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas backCanvas = new Canvas(mBackground);

私は Java ビットマップ メモリ管理の専門家ではありませんが、それには費用がかかると思います。

于 2011-05-27T20:34:22.757 に答える
1

このビデオをご覧になることをお勧めします。それは多くの点で私を助けてくれました。また、ビットマップ サイズと vm メモリ バジェットに関してもこの問題が発生しています。

そして、私の経験から: System.gc() を .recycle() の後にどこでも呼び出すという悪い習慣を身につけました。これが良くないことはわかっていますが、ビットマップが適切にリサイクルされなかった理由を何時間もデバッグした後、この強制終了を防ぐのに役立ちました.

于 2011-05-27T20:24:28.617 に答える
0

/proc/self/maps (プロセス内) または /proc/[プロセス ID]/maps を見ることができますが、それでは何が必要かがわからず、システム コールもわかりません。プロセス内でメモリを割り当てる場合、1 バイトしか割り当てない場合でも、常にシステム ページ サイズの倍数 (4kb、おそらくそれ以上) で実行されますが、内部で管理されます。将来の割り当ては、システムからさらにメモリを受け取る前に、そのブロックから行われます。

于 2011-05-27T20:15:52.477 に答える