有没有办法全面了解进程分配的所有内存?

发布于 2024-11-10 07:35:45 字数 20180 浏览 6 评论 0原文

首先是一些背景故事。我有一个基本视图,由我的应用程序的三个主要视图扩展。子视图是空的、模拟的和数字的。我将这些子视图放入网格视图(2x3)中,并将网格视图放入滑动绘图中。这个滑动抽屉是我的应用程序的关键。这是绝对必要的。滑动抽屉必须存在于每个活动中,因此当活动发生变化时,我只需将状态存储在应用程序中,并在新活动加载时检索它。

当应用程序打开时,gridview 创建六个空视图并将它们添加到其适配器中。现在,虽然所有视图都是空的,但应用程序可以完美运行。我可以浏览这些活动并执行该应用程序具有的所有其他功能。当我从事相同的活动时,我可以根据自己的喜好创建模拟和数字视图。他们移动、删除并正确执行所有功能。但是,一旦我切换到另一项活动并且,即使网格视图中有一个模拟或数字视图,应用程序也会因OutOfMemoryError:位图大小超出虚拟机预算而崩溃。

模拟视图和数字视图都会为自己创建两个位图。一个是视图的背景,另一个是视图的独特外观,很少发生变化,因此更适合作为位图。两个位图都相当小(在我的测试 Evo 上为 221x221 像素)。这让我觉得我没有在活动变化时正确地回收它们。所以我回去检查所有东西是否都被清理干净,并制定了一个方法来完全破坏每个视图。每个变量都设置为 null,并且当活动暂停时,所有位图都会被回收。 (注意:使用记录器,我验证了 onPause 以及我的 destroy 方法确实被调用。)

现在 - 几天后 - 我仍然无法弄清楚为什么会抛出这个内存错误。我花了不计其数的时间查看 DDMS 和内存跟踪器,它很可能是有史以来最无用的东西。我完全厌倦了 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);*/
    }
}

至于通过应用程序传递的状态,我将视图类型和视图所代表的caster的字符串名称放入其中哈希图。我将该哈希图传递给 gridview,然后将所有六个映射放入一个数组中,该数组将表示 gridview 中视图的位置。然后该数组保存在应用程序中并根据需要进行检索。

这是网格视图。我越想越觉得这堂课可能是问题所在。

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();                                                               
    }

First some backstory. I have a base vew that three of my app's primary views extend. The child views are empty, analog and digital. I place these child view into a gridview (2x3) and the gridview into a slidingdraw. This sliding drawer is the crux of my app. It is absolutely necessary. The sliding drawer must be in every activity, so on activity changes, I just store the state in the aapplication and retrieve it when the new activity loads.

When the app opens, the gridview creates six empty view and adds them to its adapter. Now while all of the views are empty the app works flawlessly. I can wander through the activities and do all of the other function that the app has. And while I stay in the same activity, I can create analog and digital views to my hearts content. They move, delete and do all their functions properly. But as soon as I go to switch to another activity AND I have even one analog or digital view in the gridview, the app crashes via OutOfMemoryError: bitmap size exceeds VM Budget.

Both the analog and digital view create two bitmaps for them selves. One is a the background for the view and the other is the unique look of the view that changes so rarely, it is better suited as a bitmap. Both bitmaps are fairly small (221x221 px on my test Evo). This made me think that I wasn't recycling them properly on activity changes. So I went back and checked that everything was being cleaned up and made a method that completely destroyed each view. Every variable is set to null and all bitmaps are recycled when ever the activity pauses. (Note: Using the logger, I verified that onPause is indeed being called as well as my destroy method.)

Now - days later - I still can't figure out why this memory error is being thrown. I have spent an unaccountable amount of time viewing the DDMS and Memory Tracker and it is quite possibly the most useless thing ever. I am completely fed up with DDMS, I cannot get the stupid thing to tell me anything useful.

So now the question. Is there a way (method / system call or something) that I can get the complete list of allocations of a process (my apps) and print / display / save to a file / etc... it?

Edit 1 : this is in response Falmarri. I may be posting a little to much and I apologize for that. If you want to look at something more specific I am more than happy to help, there is no reason for you to tear through my code.

The clip is from the 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);
}

This is from the digital view: (cause it's smaller and still causes the error)

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);*/
    }
}

As for the state passing through the application, I put the type of view and the string name of caster the view is representing into a hashmap. I pass that hashmap to the gridview that will then put all six maps into an array that will represent the locations of the views in the gridview. That array is then held in the application and retrieved on necessity.

This is the gridview. The more I think about, this class is were I think may issues lie.

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();                                                               
    }

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

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

发布评论

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

评论(3

空心空情空意 2024-11-17 07:35:45

这是对 @mah 的回应,但评论太长了。

它总是以倍数执行
系统页面大小

这不一定是真的。尤其是使用 Android 应用程序。有许多不同的内存分配器具有不同的语义。然而,假设您谈论的是JAVA,而不是 NDK (C++),那么透明度就更低了。 Java 的虚拟机(或者更确切地说,dalvik)几乎肯定会在机器启动时过度分配内存,然后当应用程序请求少量内存时,它会从该池中将其提供给您。

如果它的池中的内存不足,它将从操作系统分配另一个块并将其添加到其池中并从那里返回一个块。

但是,如果您请求大内存块,例如您想要位图,JVM(或 dalvik 机器)很可能会使用系统的 mmap 方法,该方法映射一部分内存。根据具体情况,它可以制作私人匿名地图,然后让您访问其中的各个部分。或者,它可以将文件映射到内存中,这基本上是磁盘上内容的内存视图。这可能就是 dalvic 处理大位图分配的方式。

并提出了一种完全
销毁每个视图

首先,您实际上无法直接控制 java 中的视图。将对象设置为 null 并不会删除它。即使您假设只有对该对象的单个引用,您也必须等到垃圾收集器清理该对象的数据。


确实不可能告诉你你的问题是什么,甚至连一个提示也不可能。我真正能说的是,您可能在比您想象的更多的地方为位图分配空间。或者您将对它们的引用保存在未清理的地方。

我只是将状态存储在
应用程序并在以下情况下检索它
新活动加载。

这是相当模糊的,但我首先看看你在这方面做了什么。比如说,如果您将位图作为可解析对象传递,则可能会根据需要分配 3-4 倍的空间。通过可解析接口发送大型自定义对象和位图非常昂贵。

我会建议以下几件事之一。您可以延迟加载位图。也就是说,永远不要将它们存储在任何地方。仅在需要时将其拉起。这可能是一个解决方案,因为它可能是您认为自己比编译器更聪明的情况。但我保证编译器在高效内存使用方面比你更聪明。

--

或者,您可以尝试相反的方法,仅在应用程序加载时加载位图,并确保每次显示它时,您不会创建新的位图或任何内容。这样它只会在内存中存在一次,如果它确实太大,你就会很早就崩溃并在一个已知的地方。

--

最后,如果您确实找不到解决方案,并且您确实认为您的 java 进程确实内存不足,您可以重写 NDK 中处理位图视图的部分。这样,如果您没有明确执行此操作,它们就不会被垃圾收集和重新创建。

--

现在问题来了。有办法吗
(方法/系统调用什么的)
我可以获得完整的列表
进程的分配(我的应用程序)和
打印/显示/保存到文件/
等等...它?

我确信有。但是,这是一个很大的但是,它无论如何都不会容易。您可能需要做的就是用跟踪谁请求内存的版本替换系统的 glibc(即专门的 malloc 函数)。然而,即使这样也会被java虚拟机混淆。


长话短说,发布一些代码,尤其是操作和保存视图和位图的部分。

更新

只要浏览一下您的代码,我就会检查 regenerate() 被调用的频率,特别是因为:

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

我不是 java 位图内存方面的专家管理,但我的猜测是这会很昂贵。

This is sort of in response to @mah, but it's too long for a comment.

it is always performed in multiples of
the system page size

This is not necessarily true. Especially with an android app. There are many different memory allocators with different semantics. However, assuming you're talking about JAVA, as opposed to the NDK (C++), there's even less transparency. Java's virtual machine (or rather, dalvik) will almost certainly over-allocate memory on machine start up and then when the app requests small bits of memory, it will give it to you from that pool.

If it runs out of memory from its pool, it will allocate another block from the OS and add that to its pool and return a block from there.

However, if you request LARGE memory blocks, such as you want for a bitmap for example, the JVM (or dalvik machine) will most likely use the system's mmap method, which maps a portion of memory. Depending on the exact circumstances, it could do a private anonymous map and then give you access to sections of that. Or, it can map a file into memory which is basically a memory view of what's on the disk. This is probably how dalvic handles large bitmap allocations.

and made a method that completely
destroyed each view

First, you really have no direct control over that in java. Setting an object to null doesn't delete it. Even if you assume that you only have a single reference to that object, you have to wait until the garbage collector cleans up the object's data.


It's really impossible to tell you what your problem is, or really even a hint. All I can really say is that you're probably allocating space for your bitmaps in more places than you thing. Or you're holding references to them somewhere that they aren't being cleaned.

I just store the state in the
application and retrieve it when the
new activity loads.

This is pretty vague, but I would first take a look at what you're doing in this. If, say, you're passing bitmaps as parsable objects, you're probably allocating 3-4x the space as necessary. Sending large custom objects and bitmaps through parsable interfaces is EXTREMELY expensive.

I would suggest one of a few things. Either you can lazily load your bitmaps. That is, don't store them anywhere ever. Only pull them up when you need them. This MIGHT be a solution because it could be a case where you think you're outsmarting the compiler. But I guarantee that the compiler is smarter than you at efficient memory usage.

--

Or, you could try the opposite and ONLY load the bitmap on application load, and make sure that each time you display it, you're not creating a new bitmap or anything. That way it's only ever in memory once, and if it truly is too big, you crash early and in a known place.

--

Finally, if you really can't find a solution, and you really think your java process is truly running out of memory, you could re-write the portion that handles the bitmap views in the NDK. That way they won't be garbage collected and recreated without you explicitly doing it.

--

So now the question. Is there a way
(method / system call or something)
that I can get the complete list of
allocations of a process (my apps) and
print / display / save to a file /
etc... it?

I'm sure there is. But, and this is a big but, it will NOT be by ANY means easy. What you'll probably have to do is replace your system's glibc (that is, the malloc functions specifically) with versions that track who requests memory. However, even this will be obfuscated by the java virtual machine.


Long story short, post some of your code, especially the portions where you're manipulating and saving your views and bitmaps.

Update:

Just from glancing at your code, I would check how often regenerate() is being called, specifically because of this:

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

I'm not an expert on java bitmap memory management, but my guess is that that would be expensive.

南薇 2024-11-17 07:35:45

我建议您观看此视频
它在很多方面帮助了我,而且我也遇到了位图大小和虚拟机内存预算的问题。

根据我的经验:我养成了在 .recycle() 之后到处调用 System.gc() 的坏习惯。我知道这不好,但它帮助我防止了这种强制关闭,经过几个小时的调试为什么我的位图没有正确回收。

I suggest you to take a look at this Video.
It helped me in many ways, and I´m also experiencing this problems with Bitmap sizes and vm memory budgets.

And from my experience: I developed the bad habit to call System.gc() everywhere after .recycle(). I know this isn´t good, but it helped me to prevent this force-closes, after hours of debugging why my bitmaps weren´t recycled properly.

妄断弥空 2024-11-17 07:35:45

您可以查看 /proc/self/maps (在进程内)或 /proc/[process id]/maps,但这不太可能告诉您您想要什么,并且没有系统调用会告诉您。当您在进程中分配内存时,即使您只分配 1 个字节,它也始终以系统页面大小的倍数(4kb,也许更多)执行 - 但它随后会在内部进行管理。在从系统接收更多内存之前,未来的分配来自该块。

You can look at /proc/self/maps (within the process) or /proc/[process id]/maps, but that's not likely to tell you what you want, and no system call will. When you allocate memory within your process, it is always performed in multiples of the system page size (4kb, perhaps more), even if you allocate only 1 byte -- but it then becomes internally managed. Future allocations come from that block before more memory is received from the system.

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