设置视图背景导致 OutOfMemoryError
Setting view background causes OutOfMemoryError
我只是想更改应用程序的背景图片,它还没有任何内容,我只是想将主要 activity 的背景更改为图片。
它在预览中显示正确,但是当我在模拟器或物理设备上 运行 它时,我收到此错误。
Throwing OutOfMemoryError "Failed to allocate a 604786188 byte
allocation with 4194208 free bytes and 230MB until OOM"
图像小于 1.5MB,所以我不知道为什么它会 运行 内存不足或试图分配这么多。
我所做的只是打开一个空白项目并更改相关布局的背景。
奇怪的是,如果我 运行 该应用程序在 nexus 4 模拟器上运行正常,但即使在 android 上的相同版本上的 nexus 5 也会抛出此错误。
编辑
不再出现上述错误,而是在启动应用程序时出现以下错误,无论使用何种模拟器。
FATAL EXCEPTION: main
Process: com.jacksteel.comp4, PID: 2164
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.jacksteel.comp4/com.jacksteel.comp4.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.Window.findViewById(int)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2236)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
at android.app.ActivityThread.access0(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.Window.findViewById(int)' on a null object reference
at android.app.Activity.findViewById(Activity.java:2072)
at com.jacksteel.comp4.MainActivity.<init>(MainActivity.java:20)
at java.lang.reflect.Constructor.newInstance(Native Method)
at java.lang.Class.newInstance(Class.java:1606)
at android.app.Instrumentation.newActivity(Instrumentation.java:1066)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2226)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
at android.app.ActivityThread.access0(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
编辑 2 源代码
package com.jacksteel.comp4;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
private RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),R.drawable.redboxes,Bg.getWidth(), Bg.getHeight());
Resources res = getResources();
BitmapDrawable backgroundDrawable = new BitmapDrawable(res, bitmap);
Bg.setBackgroundDrawable(backgroundDrawable);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
编辑 3 更新
更新源代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);
Bg.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Bg.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.redboxes, Bg.getWidth(), Bg.getHeight());
Resources res = getResources();
BitmapDrawable backgroundDrawable = new BitmapDrawable(res, bitmap);
Bg.setBackgroundDrawable(backgroundDrawable);
}
});
}
更新错误
FATAL EXCEPTION: main
Process: com.jacksteel.comp4, PID: 1872
java.lang.OutOfMemoryError: Failed to allocate a 107347980 byte allocation with 1048576 free bytes and 63MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:467)
at com.jacksteel.comp4.MainActivity.decodeSampledBitmapFromResource(MainActivity.java:93)
at com.jacksteel.comp4.MainActivity.onGlobalLayout(MainActivity.java:27)
at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:912)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1881)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5885)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
at android.view.Choreographer.doCallbacks(Choreographer.java:580)
at android.view.Choreographer.doFrame(Choreographer.java:550)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
更新
您应该将要使用 BitmapFactory.decodeResource()
加载的图像放到 drawable-nodpi
目录中,以避免从 drawable-<density>
目录名称中使用的密度缩放到您的设备屏幕密度。
在你的情况下,你将图像放在 drawable-hdpi
目录中,并且你的设备有 xxhdpi
屏幕,所以在 BitmapFactory.decodeResource()
中 inSampleSize=2
图像被放大了 2 倍(来自 hdpi
到 xxhdpi
) 并缩小 2 倍(按 inSampleSize
)保持原始大小并导致应用程序崩溃 OutOfMemoryError
。
原始答案
1.5MB是压缩后的图片大小。当您将其加载到 ImageView
或其他视图的背景时,将隐式创建 Bitmap
。在 Bitmap
中,图像的每个像素占用 4 个字节。因此,如果您的图像的分辨率为 4096x4096 像素,那么它需要 64 兆字节的内存,尽管图像可以用单一颜色填充并且压缩为 png 或 jpeg 只需要几千字节。
您应该确定视图的实际大小并加载缩放图像:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Bitmap bitmap = ImageUtils.decodeSampledBitmapFromFile(backgroundImageFile, view.getWidth(), view.getHeight());
view.setBackground(new BitmapDrawable(getResources(), bitmap));
}
});
我的实用方法是从 File
:
加载缩放的 Bitmap
public class ImageUtils {
public static Bitmap decodeSampledBitmapFromFile(File file, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.floor((float) height / (float) reqHeight);
final int widthRatio = Math.floor((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
}
您可以使用其他 BitmapFactory
方法从流、资源和字节数组中解码 Bitmap
。
我只是想更改应用程序的背景图片,它还没有任何内容,我只是想将主要 activity 的背景更改为图片。 它在预览中显示正确,但是当我在模拟器或物理设备上 运行 它时,我收到此错误。
Throwing OutOfMemoryError "Failed to allocate a 604786188 byte allocation with 4194208 free bytes and 230MB until OOM"
图像小于 1.5MB,所以我不知道为什么它会 运行 内存不足或试图分配这么多。
我所做的只是打开一个空白项目并更改相关布局的背景。
奇怪的是,如果我 运行 该应用程序在 nexus 4 模拟器上运行正常,但即使在 android 上的相同版本上的 nexus 5 也会抛出此错误。
编辑
不再出现上述错误,而是在启动应用程序时出现以下错误,无论使用何种模拟器。
FATAL EXCEPTION: main
Process: com.jacksteel.comp4, PID: 2164
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.jacksteel.comp4/com.jacksteel.comp4.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.Window.findViewById(int)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2236)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
at android.app.ActivityThread.access0(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.Window.findViewById(int)' on a null object reference
at android.app.Activity.findViewById(Activity.java:2072)
at com.jacksteel.comp4.MainActivity.<init>(MainActivity.java:20)
at java.lang.reflect.Constructor.newInstance(Native Method)
at java.lang.Class.newInstance(Class.java:1606)
at android.app.Instrumentation.newActivity(Instrumentation.java:1066)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2226)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
at android.app.ActivityThread.access0(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
编辑 2 源代码
package com.jacksteel.comp4;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;
public class MainActivity extends AppCompatActivity {
private RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),R.drawable.redboxes,Bg.getWidth(), Bg.getHeight());
Resources res = getResources();
BitmapDrawable backgroundDrawable = new BitmapDrawable(res, bitmap);
Bg.setBackgroundDrawable(backgroundDrawable);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
编辑 3 更新
更新源代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);
Bg.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Bg.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.redboxes, Bg.getWidth(), Bg.getHeight());
Resources res = getResources();
BitmapDrawable backgroundDrawable = new BitmapDrawable(res, bitmap);
Bg.setBackgroundDrawable(backgroundDrawable);
}
});
}
更新错误
FATAL EXCEPTION: main
Process: com.jacksteel.comp4, PID: 1872
java.lang.OutOfMemoryError: Failed to allocate a 107347980 byte allocation with 1048576 free bytes and 63MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:467)
at com.jacksteel.comp4.MainActivity.decodeSampledBitmapFromResource(MainActivity.java:93)
at com.jacksteel.comp4.MainActivity.onGlobalLayout(MainActivity.java:27)
at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:912)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1881)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5885)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
at android.view.Choreographer.doCallbacks(Choreographer.java:580)
at android.view.Choreographer.doFrame(Choreographer.java:550)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
更新
您应该将要使用 BitmapFactory.decodeResource()
加载的图像放到 drawable-nodpi
目录中,以避免从 drawable-<density>
目录名称中使用的密度缩放到您的设备屏幕密度。
在你的情况下,你将图像放在 drawable-hdpi
目录中,并且你的设备有 xxhdpi
屏幕,所以在 BitmapFactory.decodeResource()
中 inSampleSize=2
图像被放大了 2 倍(来自 hdpi
到 xxhdpi
) 并缩小 2 倍(按 inSampleSize
)保持原始大小并导致应用程序崩溃 OutOfMemoryError
。
原始答案
1.5MB是压缩后的图片大小。当您将其加载到 ImageView
或其他视图的背景时,将隐式创建 Bitmap
。在 Bitmap
中,图像的每个像素占用 4 个字节。因此,如果您的图像的分辨率为 4096x4096 像素,那么它需要 64 兆字节的内存,尽管图像可以用单一颜色填充并且压缩为 png 或 jpeg 只需要几千字节。
您应该确定视图的实际大小并加载缩放图像:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Bitmap bitmap = ImageUtils.decodeSampledBitmapFromFile(backgroundImageFile, view.getWidth(), view.getHeight());
view.setBackground(new BitmapDrawable(getResources(), bitmap));
}
});
我的实用方法是从 File
:
Bitmap
public class ImageUtils {
public static Bitmap decodeSampledBitmapFromFile(File file, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.floor((float) height / (float) reqHeight);
final int widthRatio = Math.floor((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
}
您可以使用其他 BitmapFactory
方法从流、资源和字节数组中解码 Bitmap
。