Сначала немного предыстории. У меня есть базовое представление, которое расширяет три основных вида моего приложения. Дочерние виды являются пустыми, аналоговыми и цифровыми. Я помещаю эти дочерние виды в вид сетки (2x3), а вид сетки в скользящий рисунок. Этот выдвижной ящик - суть моего приложения. Это абсолютно необходимо. Выдвижной ящик должен быть в каждом действии, поэтому при изменении действия я просто сохраняю состояние в приложении и извлекаю его при загрузке нового действия.
Когда приложение открывается, gridview создает шесть пустых представлений и добавляет их в свой адаптер. Теперь, когда все представления пусты, приложение работает безупречно. Я могу побродить по мероприятиям и выполнить все остальные функции, которые есть в приложении. И пока я занимаюсь одним и тем же делом, я могу создавать аналоговые и цифровые представления для своего сердца. Они перемещают, удаляют и выполняют все свои функции должным образом. Но как только я перехожу к другому виду деятельности И , у меня появляется даже одно аналоговое или цифровое представление в виде сетки, приложение вылетает через OutOfMemoryError: bitmap size exceeds VM Budget
.
Как аналоговое, так и цифровое представление создают для себя два растровых изображения. Один - это фон для представления, а другой - уникальный вид представления, который меняется так редко, что он лучше подходит в качестве растрового изображения. Оба растровых изображения довольно маленькие (221x221 px в моем тестовом Evo). Это заставило меня думать, что я не перерабатывал их должным образом при изменениях активности. Поэтому я вернулся и проверил, что все очищается, и создал метод, который полностью уничтожил каждое представление. Каждая переменная имеет значение null, и все растровые изображения перезаписываются, когда когда-либо активность приостанавливается. (Примечание: используя регистратор, я убедился, что onPause действительно вызывается так же, как и мой метод destroy.)
Сейчас, спустя несколько дней, я все еще не могу понять, почему выдается эта ошибка памяти. Я провел бесчисленное количество времени, просматривая DDMS и Memory Tracker, и это, пожалуй, самая бесполезная вещь за всю историю. Я полностью сыт по горло DDMS, я не могу заставить глупость сказать мне что-нибудь полезное.
Так что теперь вопрос. Есть ли способ (метод / системный вызов или что-то), что я могу получить полный список распределений процесса (мои приложения) и распечатать / отобразить / сохранить в файл / etc ... it?
Редактировать 1 : это ответ Фальмарри. Возможно, я публикую немного, и я прошу прощения за это. Если вы хотите взглянуть на что-то более конкретное, я с радостью помогу, у вас нет причин для того, чтобы вырывать мой код.
Клип из 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);*/
}
}
Что касается состояния, проходящего через приложение, я поместил тип представления и строковое имя кастера, который представление представляет в хэш-карту. Я передаю эту хэш-карту в 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();
}