I have a quite simple activity like this :
public class SurvivalActivity extends Activity {
private static final String KEY_LAYOUT_ID = "SurvivalLayoutId";
int mLayoutId;
private static final int[] mSurvivalLayouts = {
R.layout.survival1,
R.layout.survival2,
R.layout.survival3,
};
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent i = getIntent();
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_LAYOUT_ID)) {
mLayoutId = savedInstanceState.getInt(KEY_LAYOUT_ID, 0);
} else if (i != null) {
mLayoutId = i.getIntExtra(KEY_LAYOUT_ID, 0);
} else {
mLayoutId = 0;
}
// Layout
setContentView(mSurvivalLayouts[mLayoutId]);
// Title
Util.setTitleFont(this, findViewById(R.id.title));
// "Next" button
final View nextButton = findViewById(R.id.survival_next);
Util.setFont(this, nextButton, "Lobster.ttf");
nextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
final Intent next = new Intent(SurvivalActivity.this, SurvivalActivity.class);
next.putExtra(KEY_LAYOUT_ID, (mLayoutId + 1) % mSurvivalLayouts.length);
startActivity(next);
finish();
}
});
}
@Override
protected void onSaveInstanceState(final Bundle outState) {
outState.putInt(KEY_LAYOUT_ID, mLayoutId);
}
}
The two Util.* static methods just change the font of the TextView.
All 3 layouts are rather simple, let me show you the first :
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
style="@style/Title"
android:text="@string/surv1_title" />
<TextView
style="@style/Content"
android:text="@string/surv1_1" />
<ImageView
style="@style/Picture"
android:src="@drawable/surv_crafting" />
<TextView
style="@style/PicLegend"
android:src="@string/surv_crafting_leg" />
<ImageView
style="@style/Picture"
android:src="@drawable/surv_hache" />
<TextView
style="@style/PicLegend"
android:src="@string/surv_hache_leg" />
<ImageView
style="@style/Picture"
android:src="@drawable/surv_pioche" />
<TextView
style="@style/PicLegend"
android:src="@string/surv_pioche_leg" />
<TextView
style="@style/Content"
android:text="@string/surv1_2" />
<ImageView
style="@style/Picture"
android:src="@drawable/surv_charbon" />
<TextView
style="@style/PicLegend"
android:src="@string/surv_charbon_leg" />
<TextView
style="@style/Content"
android:text="@string/surv1_3" />
<ImageView
style="@style/Picture"
android:src="@drawable/surv_torch" />
<TextView
style="@style/PicLegend"
android:src="@string/surv_torch_leg" />
<TextView
style="@style/Content"
android:text="@string/surv1_4" />
<ImageView
style="@style/Picture"
android:src="@drawable/surv_abri" />
<TextView
style="@style/PicLegend"
android:src="@string/surv_abri_leg" />
<TextView
style="@style/Content"
android:text="@string/surv1_5" />
<Button
style="@style/SurvivalBoxNext"
android:text="@string/surv1_next" />
</LinearLayout>
</ScrollView>
As you can see it's just several TextViews with ImageViews, and a Button that triggers the load of the next activity.
I launch the app, it displays the first screen. I scroll down to the button, click, the second screen is displayed (with a hiccup). I scroll down to the button, click, and boom.
03-02 16:36:46.488 E/AndroidRuntime(10611): java.lang.RuntimeException: Unable to start activity ComponentInfo{net.bicou.myapp/net.bicou.myapp.SurvivalActivity}: android.view.InflateException: Binary XML file line #72: Error inflating class <unknown>
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.ActivityThread.access$600(ActivityThread.java:122)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1146)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.os.Handler.dispatchMessage(Handler.java:99)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.os.Looper.loop(Looper.java:137)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.ActivityThread.main(ActivityThread.java:4340)
03-02 16:36:46.488 E/AndroidRuntime(10611): at java.lang.reflect.Method.invokeNative(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611): at java.lang.reflect.Method.invoke(Method.java:511)
03-02 16:36:46.488 E/AndroidRuntime(10611): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
03-02 16:36:46.488 E/AndroidRuntime(10611): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
03-02 16:36:46.488 E/AndroidRuntime(10611): at dalvik.system.NativeStart.main(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611): Caused by: android.view.InflateException: Binary XML file line #72: Error inflating class <unknown>
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.createView(LayoutInflater.java:606)
03-02 16:36:46.488 E/AndroidRuntime(10611): at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.onCreateView(LayoutInflater.java:653)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:678)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.rInflate(LayoutInflater.java:742)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
03-02 16:36:46.488 E/AndroidRuntime(10611): at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:251)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.Activity.setContentView(Activity.java:1835)
03-02 16:36:46.488 E/AndroidRuntime(10611): at net.bicou.myapp.SurvivalActivity.onCreate(SurvivalActivity.java:34)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.Activity.performCreate(Activity.java:4465)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1919)
03-02 16:36:46.488 E/AndroidRuntime(10611): ... 11 more
03-02 16:36:46.488 E/AndroidRuntime(10611): Caused by: java.lang.reflect.InvocationTargetException
03-02 16:36:46.488 E/AndroidRuntime(10611): at java.lang.reflect.Constructor.constructNative(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611): at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.view.LayoutInflater.createView(LayoutInflater.java:586)
03-02 16:36:46.488 E/AndroidRuntime(10611): ... 25 more
03-02 16:36:46.488 E/AndroidRuntime(10611): Caused by: java.lang.OutOfMemoryError
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.Bitmap.nativeCreate(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:524)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:499)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:351)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:773)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.content.res.Resources.loadDrawable(Resources.java:1937)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.content.res.TypedArray.getDrawable(TypedArray.java:601)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.widget.ImageView.<init>(ImageView.java:119)
03-02 16:36:46.488 E/AndroidRuntime(10611): at android.widget.ImageView.<init>(ImageView.java:109)
03-02 16:36:46.488 E/AndroidRuntime(10611): ... 28 more
03-02 16:36:46.520 W/ActivityManager( 211): Force finishing activity net.bicou.myapp/.SurvivalActivity
I don't understand :
Grow heap (frag case) to 60.869MB for 3600016-byte allocation)TextViews and 10 ImageViewsThank you.
Edit: picture sizes of the first activity:
-rw-r--r-- 1 bicou staff 167197 29 fév 16:42 surv_abri.jpg 600x377 px
-rw-r--r-- 1 bicou staff 24658 29 fév 16:42 surv_charbon.png 559x277 px
-rw-r--r-- 1 bicou staff 5285 29 fév 16:42 surv_crafting.png 214x121 px
-rw-r--r-- 1 bicou staff 4190 29 fév 16:42 surv_hache.png 214x121 px
-rw-r--r-- 1 bicou staff 3809 29 fév 16:42 surv_pioche.png 214x121 px
-rw-r--r-- 1 bicou staff 2252 29 fév 16:42 surv_torch.png 215x121 px
Second activity:
-rw-r--r-- 1 bicou staff 3371 29 fév 16:42 surv_four.png 204x112 px
-rw-r--r-- 1 bicou staff 5059 29 fév 16:42 surv_glass.png 215x122 px
-rw-r--r-- 1 bicou staff 3176 29 fév 16:42 surv_malle.png 204x112 px
-rw-r--r-- 1 bicou staff 2676 29 fév 16:42 surv_pelle.png 204x112 px
-rw-r--r-- 1 bicou staff 167166 29 fév 16:42 surv_repere.png 528x331 px
-rw-r--r-- 1 bicou staff 134706 29 fév 16:42 surv_sand.jpg 854x480 px
Third activity (crashing):
-rw-r--r-- 1 bicou staff 58919 29 fév 16:42 surv_1.jpg 600x377 px
-rw-r--r-- 1 bicou staff 51144 29 fév 16:42 surv_2.jpg 600x377 px
-rw-r--r-- 1 bicou staff 46917 29 fév 16:42 surv_3.jpg 600x377 px
-rw-r--r-- 1 bicou staff 53226 29 fév 16:42 surv_4.jpg 600x377 px
-rw-r--r-- 1 bicou staff 37787 29 fév 16:42 surv_5.jpg 600x377 px
-rw-r--r-- 1 bicou staff 31050 29 fév 16:42 surv_6.jpg 600x377 px
-rw-r--r-- 1 bicou staff 38389 29 fév 16:42 surv_7.jpg 600x377 px
-rw-r--r-- 1 bicou staff 44471 29 fév 16:42 surv_8.jpg 600x377 px
And the Window background:
-rw-r--r-- 1 bicou staff 30857 29 fév 16:42 background_img.png
EDIT:
OK — I tried inflating the views directly from code in order to see if I had a buggy image. Here's my activity code (only variables + onCreate as the rest is identical)
private static final int[] mTitles = {
R.string.surv1_title, R.string.surv2_title, R.string.surv3_title
};
private static final int[][] mTextViews = {
{ R.string.surv1_1, R.string.surv1_2, R.string.surv1_3, R.string.surv1_4, R.string.surv1_5 },
{ R.string.surv2_1, R.string.surv2_2, R.string.surv2_3, R.string.surv2_4, R.string.surv2_5, R.string.surv2_6 },
{ R.string.surv3_1, R.string.surv3_2, R.string.surv3_3 }
};
private static final int[][] mImageViews = {
{ R.drawable.surv_pioche, R.drawable.surv_crafting, R.drawable.surv_hache, R.drawable.surv_charbon,
R.drawable.surv_torch, R.drawable.surv_abri },
{ R.drawable.surv_malle, R.drawable.surv_four, R.drawable.surv_pelle, R.drawable.surv_repere,
R.drawable.surv_sand, R.drawable.surv_glass },
{ R.drawable.surv_1, R.drawable.surv_2, R.drawable.surv_3, R.drawable.surv_4,
R.drawable.surv_5, R.drawable.surv_6, R.drawable.surv_7, R.drawable.surv_8 },
};
private static final int[] mNextButtons = {
R.string.surv1_next, R.string.surv2_next, R.string.surv3_next
};
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent i = getIntent();
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_LAYOUT_ID)) {
mLayoutId = savedInstanceState.getInt(KEY_LAYOUT_ID, 0);
} else if (i != null) {
mLayoutId = i.getIntExtra(KEY_LAYOUT_ID, 0);
} else {
mLayoutId = 0;
}
// Layout
//setContentView(mSurvivalLayouts[mLayoutId]);
LinearLayout ll;
ScrollView sv;
TextView tv;
ImageView iv;
Button b;
sv = new ScrollView(this);
sv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
ll.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
tv = new TextView(this);
tv.setId(R.id.title);
tv.setText(getString(mTitles[mLayoutId]));
ll.addView(tv);
for (final int textId: mTextViews[mLayoutId]) {
tv = new TextView(this);
tv.setText(getString(textId));
ll.addView(tv);
}
for (final int imgId: mImageViews[mLayoutId]) {
L.i("Creating image: "+imgId);
iv = new ImageView(this);
iv.setImageResource(imgId);
ll.addView(iv);
}
b = new Button(this);
b.setText(getString(mNextButtons[mLayoutId]));
b.setId(R.id.survival_next);
ll.addView(b);
sv.addView(ll);
setContentView(sv);
// Title
Util.setTitleFont(this, findViewById(R.id.title));
// "Next" button
final View nextButton = findViewById(R.id.survival_next);
Util.setFont(this, nextButton, "Lobster.ttf");
nextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
final Intent next = new Intent(SurvivalActivity.this, SurvivalActivity.class);
next.putExtra(KEY_LAYOUT_ID, (mLayoutId + 1) % mSurvivalLayouts.length);
startActivity(next);
finish();
}
});
}
As you can see I am just inflating the views directly, and L.i (which is an alias to Log.i(TAG, ...)) indicates what was the last ImageView inflated before any crash.
I ran the app, saw the 1st activity, then the second, and as previously it crashes while loading the third one. The last ID I saw in the logcat was the last picture R.drawable.surv_8. This is a 630x377 jpeg file, 45 kiB.
I tried to change the order of the views like this:
private static final int[][] mImageViews = {
{ R.drawable.surv_pioche, R.drawable.surv_crafting, R.drawable.surv_hache, R.drawable.surv_charbon,
R.drawable.surv_torch, R.drawable.surv_abri },
{ R.drawable.surv_malle, R.drawable.surv_four, R.drawable.surv_pelle, R.drawable.surv_repere,
R.drawable.surv_sand, R.drawable.surv_glass },
{ R.drawable.surv_8, R.drawable.surv_1, R.drawable.surv_2, R.drawable.surv_3, R.drawable.surv_4,
R.drawable.surv_5, R.drawable.surv_6, R.drawable.surv_7, },
};
So that surv_8 is first. The app is still crashing at the last picture, which is R.drawable.surv_7. So that is not an image issue.
Then I removed one of the pictures out of mImageViews[2] so that I have only 7 pictures instead of 8. The app is working as expected: the activity loads, when I click the button I go to the next one, and on the last I go to the first. No crash.
I can however tell that the activity 1 and 2 stay into memory. Indeed, have a look at the timings:
03-04 15:47:13.685 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +2s570ms
03-04 15:47:36.834 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +803ms
03-04 15:47:39.756 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +1s476ms
03-04 15:47:44.201 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +462ms
03-04 15:47:46.717 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +474ms
03-04 15:47:48.599 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +474ms
400ms instead of 1.5-2.5 sec to load the activity.
Why is this staying into memory?
=> ** Do I need to manually free all pictures upon onDestroy() ? **
=> What do you think about this: http://androidactivity.wordpress.com/2011/09/24/solution-for-outofmemoryerror-bitmap-size-exceeds-vm-budget/
With this solution I could load like 10 bitmaps globally, and I would free some while loading another activity?
I'm sorry that the answer of this question wasn't actually possible to have with the elements I gave in my question.
Indeed, as mentioned in another question here: Why are all my bitmaps upsampled 200%?, I put my pictures in the drawable folder, which, on a xhdpi screen like mine, will cause Android to upsample everything twice (to match xhdpi against mdpi), and will result in a 400% memory usage (twice the width and twice the height so 4 times as many pixels).
One more thing, since I don't want the user to be able to go back (see the finish() right after the startActivity()), here's what I put in the onPause():
private void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
This is some code I got here on StackOverflow (for example here: Drawable vs Single reusable Bitmap better with memory?).
Here are my remarks about this code and why every line is important:
setCallback(null) removes the reference to the activity, so that it can be garbage collected. If you don't do this, all the views will stay in memory.onPause, because onDestroy is not guaranteed to be called.System.gc().This is the resulting onPause code, with the main layout of your activity having the id top_layout:
@Override
protected void onPause() {
super.onPause();
unbindDrawables(findViewById(R.id.top_layout));
System.gc();
}
It might just be that the bitmap you're putting in the ImageView is very large. Here's some things you can try:
Ditch the xml layout and just inflate and display the problematic ImageView or ImageViews and see if this way you're getting the error.
Also check each of your images to see what size they actually have. The size they'll take in memory is roughly 4*width*height (in bytes, where width and height are the image's size in pixels).
Since your Bitmap instances are taking up memory on the heap, I'm imagining you're using Android 3 or above. In these versions that's where Bitmap instances go (just like any other Java instance). In Android 2.x and below, Bitmap instances were allocated in some offheap memory. Knowing this, make sure that that high heap usage you're getting is indeed from the images, and not from some other instances that are created a lot. Try debugging your constructors and see who calls what when the app starts/runs.
As per Bicou's observations, you might want to manually load your images via BitmapFactory, and in doing so maybe downsample them (via the Options object). Also, try keeping a counter of how much image bytes in memory you total by incrementing with 4*width*height of each image you're actually loading into a Bitmap Object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With