I'm trying to implement Android-wheel, a library that I can use to imitate a slot machine.
The demo project works fine, it use's android's own drawables => android.R.drawable.*
However when I change the default images to the ones I want it gives me this error :
java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
All I changed was the id's array which contains the reference to the images
from
private final int items[] = new int[] {
android.R.drawable.star_big_on,
android.R.drawable.stat_sys_warning,
android.R.drawable.radiobutton_on_background,
android.R.drawable.ic_delete
};
to
private final int items[] = new int[] {
R.drawable.sunglasses, R.drawable.ball, R.drawable.box};
Here's the rest of the code.
private class SlotMachineAdapter extends AbstractWheelAdapter {
// Image size
final int IMAGE_WIDTH = 267;
final int IMAGE_HEIGHT = 200;
// Slot machine symbols
private final int items[] = new int[] {
R.drawable.sunglasses, R.drawable.ball, R.drawable.box};
// Cached images
private List<SoftReference<Bitmap>> images;
// Layout inflater
private Context context;
/**
* Constructor
*/
public SlotMachineAdapter(Context context) {
this.context = context;
images = new ArrayList<SoftReference<Bitmap>>(items.length);
for (int id : items) {
images.add(new SoftReference<Bitmap>(loadImage(id)));
}
}
/**
* Loads image from resources
*/
private Bitmap loadImage(int id) {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), id);
Bitmap scaled = Bitmap.createScaledBitmap(bitmap, IMAGE_WIDTH, IMAGE_HEIGHT, true);
bitmap.recycle();
return scaled;
}
#Override
public int getItemsCount() {
return items.length;
}
// Layout params for image view
final LayoutParams params = new LayoutParams(IMAGE_WIDTH, IMAGE_HEIGHT);
#Override
public View getItem(int index, View cachedView, ViewGroup parent) {
ImageView img;
if (cachedView != null) {
img = (ImageView) cachedView;
} else {
img = new ImageView(context);
}
img.setLayoutParams(params);
SoftReference<Bitmap> bitmapRef = images.get(index);
Bitmap bitmap = bitmapRef.get();
if (bitmap == null) {
bitmap = loadImage(items[index]);
images.set(index, new SoftReference<Bitmap>(bitmap));
}
img.setImageBitmap(bitmap);
return img;
}
}
Here's the full stacktrace
java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:778)
at android.view.GLES20RecordingCanvas.drawBitmap(GLES20RecordingCanvas.java:117)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:393)
at android.widget.ImageView.onDraw(ImageView.java:961)
at android.view.View.draw(View.java:13765)
at android.view.View.draw(View.java:13649)
at android.view.ViewGroup.drawChild(ViewGroup.java:2940)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2810)
at android.view.View.draw(View.java:13768)
at kankan.wheel.widget.WheelView.drawItems(WheelView.java:587)
at kankan.wheel.widget.WheelView.onDraw(WheelView.java:557)
android.view.View.draw(View.java:13765)
at android.view.View.getDisplayList(View.java:12716)
at android.view.View.getDisplayList(View.java:12760)
at android.view.View.draw(View.java:13489)
at android.view.ViewGroup.drawChild(ViewGroup.java:2940)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2810)
at android.view.View.getDisplayList(View.java:12714)
at android.view.View.getDisplayList(View.java:12760)
at android.view.View.draw(View.java:13489)
at android.view.ViewGroup.drawChild(ViewGroup.java:2940)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2810)
at android.view.View.draw(View.java:13768)
at android.view.View.getDisplayList(View.java:12716)
at android.view.View.getDisplayList(View.java:12760)
at android.view.View.draw(View.java:13489)
at android.view.ViewGroup.drawChild(ViewGroup.java:2940)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2810)
at android.view.View.getDisplayList(View.java:12714)
at android.view.View.getDisplayList(View.java:12760)
at android.view.View.draw(View.java:13489)
at android.view.ViewGroup.drawChild(ViewGroup.java:2940)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2810)
at android.view.View.getDisplayList(View.java:12714)
at android.view.View.getDisplayList(View.java:12760)
at android.view.View.draw(View.java:13489)
at android.view.ViewGroup.drawChild(ViewGroup.java:2940)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2810)
at android.view.View.draw(View.java:13768)
at android.widget.FrameLayout.draw(FrameLayout.java:467)
at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2394)
at android.view.View.getDisplayList(View.java:12716)
at android.view.View.getDisplayList(View.java:12760)
at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1144)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2267)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2139)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1950)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4464)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
at android.view.Choreographer.doCallbacks(Choreographer.java:555)
at android.view.Choreographer.doFrame(Choreographer.java:525)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4895) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:994) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:761) at dalvik.system.NativeStart.main(Native Method)
I think it is beacause you do bitmap.recycle() too frequently,while the system.gc() is not timely called,so variable bitmap is recycle(), but not really cleared by the system, we can't use it to load the new image. so, just comment bitmap.recycle(),Let the system recycle the gabage by itself.
Try with this -
if(bitmap!=null)
{
bitmap.recycle();
bitmap=null;
}
Related
I want to add image at runtime in ARCore database. So, in my MainActivity.java, I have a button Registered Image, which on Click , added the image in ARCore database.
MainActivity.java :
public class MainActivity extends AppCompatActivity {
private CustomArFragment arFragment;
private TextView textView;
private AugmentedImageDatabase aid;
private Frame frame;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
arFragment = (CustomArFragment) getSupportFragmentManager().findFragmentById(R.id.arFragment);
textView = findViewById(R.id.textView);
arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdate);
findViewById(R.id.registeredBtn).setOnClickListener(v -> {
if(ActivityCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
return;
}
registeredImage();
});
}
private static byte[] NV21toJPEG(byte[] nv21, int width, int height) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
return out.toByteArray();
}
private static byte[] YUV_420_888toNV21(Image image) {
byte[] nv21;
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
nv21 = new byte[ySize + uSize + vSize];
//U and V are swapped
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
return nv21;
}
// This function triggered when Registered image button clicked
private void registeredImage() {
File file = new File(getExternalFilesDir(null) + "/db.imgdb");
Frame CurrFrame = frame;
Image currentImage;
int idx = -1;
try {
currentImage = CurrFrame.acquireCameraImage();
byte[] data = null;
data = NV21toJPEG(YUV_420_888toNV21(currentImage),
currentImage.getWidth(), currentImage.getHeight());
FileOutputStream outputStream = new FileOutputStream(file);
Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
idx = aid.addImage("earth",bitmap);
aid.serialize(outputStream);
outputStream.close();
Toast.makeText(this, "image Registered", Toast.LENGTH_SHORT).show();
} catch (NotYetAvailableException | IOException e) {
e.printStackTrace();
}
}
private void onUpdate(FrameTime frameTime) {
frame = arFragment.getArSceneView().getArFrame();
Collection<AugmentedImage> images = frame.getUpdatedTrackables(AugmentedImage.class);
for(AugmentedImage image : images){
if(image.getTrackingMethod() == AugmentedImage.TrackingMethod.FULL_TRACKING){
if(image.getName().equals("test")){
textView.setText("Test is visible");
}
else if(image.getName().equals("earth")){
textView.setText("earth is visible");
}
}
}
}
}
When I clicked on button, the app crashes and image didnot add in database. Following are the errors when idx = aid.addImage("earth",bitmap); line called in registeredImage() function.
2020-10-05 14:11:39.738 31013-31013/com.example.artag E/native: error_policy_util.cc:261
################ ARCore Native Error ##################
BUILD_CHANGELIST:331869482
BUILD_BASELINE_CHANGELIST:331085015
################### Stack Trace Begin ################
ARCoreError: third_party/arcore/ar/planar_targets/augmented_image_database_utils.cc:58 https://cs.corp.google.com/piper///depot/google3/third_party/arcore/ar/planar_targets/augmented_image_database_utils.cc?g=0&l=58
ARCoreError: third_party/arcore/ar/core/c_api/augmented_image_database_c_api.cc:133 https://cs.corp.google.com/piper///depot/google3/third_party/arcore/ar/core/c_api/augmented_image_database_c_api.cc?g=0&l=133
################### Stack Trace End #################
2020-10-05 14:11:39.739 31013-31013/com.example.artag D/AndroidRuntime: Shutting down VM
2020-10-05 14:11:39.742 31013-31013/com.example.artag E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.artag, PID: 31013
com.google.ar.core.exceptions.ImageInsufficientQualityException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at com.google.ar.core.Session.throwExceptionFromArStatus(Session.java:101)
at com.google.ar.core.AugmentedImageDatabase.nativeAddImage(Native Method)
at com.google.ar.core.AugmentedImageDatabase.addImage(AugmentedImageDatabase.java:5)
at com.example.artag.MainActivity.registeredImage(MainActivity.java:130)
at com.example.artag.MainActivity.lambda$onCreate$0$MainActivity(MainActivity.java:72)
at com.example.artag.-$$Lambda$MainActivity$5zEkixen6UibjSKLs5AkDUICWdM.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7333)
at android.widget.TextView.performClick(TextView.java:14160)
at android.view.View.performClickInternal(View.java:7299)
at android.view.View.access$3200(View.java:846)
at android.view.View$PerformClick.run(View.java:27773)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6990)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
Please help to find out the problem,
Thanks :)
The image that you attempted to add to the database does not have enough features.
Check https://developers.google.com/ar/reference/java/com/google/ar/core/exceptions/ImageInsufficientQualityException#ImageInsufficientQualityException()
for the complete specification of the errors.
If you want users to upload images, I recommend using a try-catch-block and letting the user know about the error instead of having the app crash.
I'm trying blur some images 1024x1024 and sometimes I'm getting java.lang.OutOfMemoryError and i don't know why.
I'm testing directly in my tablet Android 4.1.2 and I have always 1.5gb Ram free.
Here the class I'm using:
package com.example.playerclient.blur;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v8.renderscript.*;
public class BlurBuilder {
private static final float BITMAP_SCALE = 0.6f;
public static Bitmap blur(Context context, Bitmap image, float blurRadius) {
Bitmap outputBitmap = null;
if (image != null) {
if (blurRadius == 0) {
return image;
}
if (blurRadius < 1) {
blurRadius = 1;
}
if (blurRadius > 25) {
blurRadius = 25;
}
int width = Math.round(image.getWidth() * BITMAP_SCALE);
int height = Math.round(image.getHeight() * BITMAP_SCALE);
Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
outputBitmap = Bitmap.createBitmap(inputBitmap);
RenderScript rs = RenderScript.create(context);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
theIntrinsic.setRadius(blurRadius);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
}
return outputBitmap;
}
}
Here where I call the method:
public class MusicaActivity extends AppCompatActivity implements View.OnClickListener {
// Others methods here.
private void mostraDadosMusica() {
Intent intent = getIntent();
Musica musica = (Musica)intent.getSerializableExtra(MainActivity.VISUALIZA_PLAYER);
// Where i'm having problems.
Bitmap originalBmp = BitmapFactory.decodeResource(getResources(), musica.getAlbum().getIdCapa());
Bitmap resultBmp = BlurBuilder.blur(this, originalBmp, 15f);
backgroundImageView.setImageBitmap(resultBmp);
}
}
Ande here the exception message:
11-04 22:05:07.453 18928-18928/com.example.playerclient E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:636)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:484)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:512)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:542)
at com.example.playerclient.activity.MusicaActivity.mostraDadosMusica(MusicaActivity.java:48)
at com.example.playerclient.activity.MusicaActivity.onCreate(MusicaActivity.java:87)
at android.app.Activity.performCreate(Activity.java:5188)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1094)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2074)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2135)
at android.app.ActivityThread.access$700(ActivityThread.java:140)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1237)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4921)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
at dalvik.system.NativeStart.main(Native Method)
What I'm trying do to is using a custom CursorAdapter, in order to choose which layout to show and also to populate View items such as TextViews and also ImageView.
Now not in all the listview items there gonna be an image.
My CursorAdapter code is -
private static class ViewHolder {
TextView mesg;
TextView mesg2;
TextView send;
ImageView myImage;
}
public class ChatCursorAdapter extends CursorAdapter implements OnClickListener {
public ChatCursorAdapter(Context context, Cursor c) {
super(context, c, 0);
}
#Override
public int getCount() {
return getCursor() == null ? 0 : super.getCount();
}
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int _position) {
Cursor cursor = (Cursor) getItem(_position);
return getItemViewType(cursor);
}
private int getItemViewType(Cursor cursor) {
String sender = cursor.getString(2);
String saveUser = user;
if (saveUser.equalsIgnoreCase(sender)){
return 0;
}else{
return 1;
}
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
String msg = cursor.getString(3);
String msg2 = cursor.getString(4);
String sender = cursor.getString(2);
holder.mesg.setText(getSmiledText(Main.this,msg));
holder.mesg2.setText(getSmiledText(Main.this,msg2));
holder.send.setText(sender);
picPath = cursor.getString(8);
if(picPath == null || picPath.isEmpty()){
holder.myImage.setVisibility(View.GONE);
}else{
File file = new File(picPath);
if(file.exists()){
new AsyncImageSetter(holder.myImage, picPath).execute();
holder.myImage.setOnClickListener(this);
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
ViewHolder holder = new ViewHolder();
View itemLayout = null;
switch(getItemViewType(cursor)){
case 0:
itemLayout = getLayoutInflater().inflate(R.layout.msg_item1,parent, false);
break;
case 1:
itemLayout = getLayoutInflater().inflate(R.layout.msg_item13, parent,false);
break;
}
itemLayout.setTag(holder);
holder.mesg = (TextView) itemLayout.findViewById(R.id.text_start);
holder.mesg2 = (TextView) itemLayout.findViewById(R.id.text_end);
holder.send = (TextView) itemLayout.findViewById(R.id.text_from);
holder.myImage = (ImageView) itemLayout.findViewById(R.id.imageView_msgpic);
return itemLayout;
}
}
As you can see when there a need to load an image to the ImageView, I'm using asynctask in order to let the flow of the list view scrolling to be much more smoother.
This how the asynctask code is -
public class AsyncImageSetter extends AsyncTask<Void, Void, Void> {
private ImageView img;
private String path;
private Bitmap bm;
public AsyncImageSetter(ImageView img, String path) {
this.img = img;
this.path = path;
}
#Override
protected Void doInBackground(Void... params) {
bm = BitmapFactory.decodeFile(path);
bm = setImageToImageView(path);
return null;
}
#Override
protected void onPostExecute(Void result) {
img.setTag(path);
img.setImageBitmap(bm);
//img.setVisibility(View.VISIBLE);
super.onPostExecute(result);
}
}
Well the thing is that it sure made the scrolling alot more smoother, but it seems to make the app crash a lot of times.
The logcat says the next -
03-24 17:07:34.125: E/AndroidRuntime(15422): FATAL EXCEPTION: AsyncTask #2
03-24 17:07:34.125: E/AndroidRuntime(15422): java.lang.RuntimeException: An error occured while executing doInBackground()
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.os.AsyncTask$3.done(AsyncTask.java:299)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.util.concurrent.FutureTask.run(FutureTask.java:239)
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.lang.Thread.run(Thread.java:841)
03-24 17:07:34.125: E/AndroidRuntime(15422): Caused by: java.lang.OutOfMemoryError
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:623)
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:417)
03-24 17:07:34.125: E/AndroidRuntime(15422): at at.vcity.androidim.MainChat$AsyncImageSetter.doInBackground(MainChat.java:3356)
03-24 17:07:34.125: E/AndroidRuntime(15422): at at.vcity.androidim.MainChat$AsyncImageSetter.doInBackground(MainChat.java:1)
03-24 17:07:34.125: E/AndroidRuntime(15422): at android.os.AsyncTask$2.call(AsyncTask.java:287)
03-24 17:07:34.125: E/AndroidRuntime(15422): at java.util.concurrent.FutureTask.run(FutureTask.java:234)
03-24 17:07:34.125: E/AndroidRuntime(15422): ... 4 more
So what am I doing wrong here?
Thanks for any kind of help
It looks to me like the Bitmap you are trying to store in memory is too large to be stored in your tablet/emulator's memory. Here;
bm = BitmapFactory.decodeFile(path);
See if the code works with a much smaller file than the one that is coming from your current path (also see if it works with a smaller list). This could also be an instance of 'The straw that broke the camel's back'. If your current application is already very memory intensive you might have to go through your current code and optimise for memory management.
Because your are creating an AsyncTask for each item in the ListView, your trying to hold that many images in memory at once. So you might need to find another way to do it. You might need to try loading the image's thumbnail into your ImageViews instead.
An example of getting an images thumbnail.
I hope this helps.
An Example Implementation
Just to run through a potential way to implement the above linked thumbnail example, what I might do to get the thumbnail is I could store the context passed to your ChatCursorAdapter by adding the following as a class variable (and instantiate it in the constructor);
Context ourContext;
public ChatCursorAdapter(Context context, Cursor c) {
super(context, c, 0);
ourContext = context;
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
...
new AsyncImageSetter(holder.myImage, picPath, ourContext.getContentResolver()).execute();
...
}
Then (as above) I might use the Context to get the ContentResolver instance and pass that instance into your AsyncTask via it's constructor. Then we could add the method in the example code to get the thumbnail into the custom AsyncTask, which might look something like this;
ContentResolver cr;
public AsyncImageSetter(ImageView img, String path, ContentResolver cr) {
this.img = img;
this.path = path;
this.cr = cr;
}
...
#Override
protected Void doInBackground(Void... params) {
try{
bm = getThumbnail(cr, path);
}catch(Exception e){}
return null;
}
private Bitmap getThumbnail(ContentResolver cr, String path) throws Exception {
Cursor ca = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { MediaStore.MediaColumns._ID }, MediaStore.MediaColumns.DATA + "=?", new String[] {path}, null);
if (ca != null && ca.moveToFirst()) {
int id = ca.getInt(ca.getColumnIndex(MediaStore.MediaColumns._ID));
ca.close();
return MediaStore.Images.Thumbnails.getThumbnail(cr, id, MediaStore.Images.Thumbnails.MICRO_KIND, null );
}
ca.close();
return null;
}
The cause is in your stacktrace: java.lang.OutOfMemoryError. It looks like one of two things -- either your images are too large or you are leaking memory (or both).
My recommendation would be to use one of the several libraries for doing background image loading. Here are a couple of suggestions:
https://github.com/nostra13/Android-Universal-Image-Loader
https://github.com/square/picasso
OK, I am developing an android application that takes a photo using the camera, then uses that image in an ImageView.
The problem that I'm having is that I keep getting an java.lang.OutOfMemoryError exception when I try and load the image.
Code to start camera intent:
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
AlertDialog.Builder builder = new AlertDialog.Builder(AddVehicleActivity.this);
builder.setTitle("Select A Method");
builder.setItems(R.array.array_PhotoMethods, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
switch(which) {
case 0 :
IMAGE_LOCATION = Utility.setFileName(_session); //Returns image path
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(IMAGE_LOCATION)));
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST);
break;
}
}
});
builder.show();
}
});
From my understanding, this will allow the image to be saved at the full resolution of the camera which I can confirm through the image details in the gallery. Using my Samsung SIII, my image is about 3.3MB. So, I am able to use the camera and store the image I take using the camera to path I specify. The problem comes when trying to use the image.
To load the image, I use the following:
public static Bitmap Shrink(String file, int width, int height) {
BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
int heightRatio = (int)Math.ceil(bmpFactoryOptions.outHeight/(float)height);
int widthRatio = (int)Math.ceil(bmpFactoryOptions.outWidth/(float)width);
if (heightRatio > 1 || widthRatio > 1) {
bmpFactoryOptions.inSampleSize = (heightRatio > widthRatio) ? heightRatio : widthRatio;
}
return BitmapFactory.decodeFile(file, bmpFactoryOptions);
}
I have rewritten this functions a few different ways and found out that sometimes, I get the memory error when calling BitmapFactory.decodeFile
My stack trace looks like this:
E/dalvikvm-heap(8355): Out of memory on a 31961104-byte allocation.
java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:619)
at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:385)
at com.autopilot.com.objects.Images.Shrink(Images.java:191)
at com.autopilot.com.adapters.ImageAdapter.getView(ImageAdapter.java:90)
at android.widget.AbsListView.obtainView(AbsListView.java:2437)
at android.widget.GridView.onMeasure(GridView.java:1030)
at android.view.View.measure(View.java:15395)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4826)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1038)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:576)
at android.view.View.measure(View.java:15395)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4826)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1396)
at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1038)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:576)
at android.view.View.measure(View.java:15395)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4826)
at android.widget.LinearLayout.forceUniformWidth(LinearLayout.java:926)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:906)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15395)
at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:617)
at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:399)
at android.view.View.measure(View.java:15395)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4826)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.view.View.measure(View.java:15395)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:833)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:574)
at android.view.View.measure(View.java:15395)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4826)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2362)
at android.view.View.measure(View.java:15395)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1985)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1226)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1399)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1119)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4553)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
at android.view.Choreographer.doCallbacks(Choreographer.java:555)
at android.view.Choreographer.doFrame(Choreographer.java:525)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4950)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1004)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:771)
at dalvik.system.NativeStart.main(Native Method)
From tracing this through, the memory exception occurs anytime that the original bitmap is loaded. I cannot find a way to set the saved image size and when I try to save the bitmap from the intent extras, it's never big enough, usually thumb nail size.
Any help would be greatly appreciated!
OK, I found the answer, thank you sunil!
public static Bitmap Shrink(String file, int width, int height) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, options);
options.inSampleSize = calcSize(options, width, height);
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(file, options);
return bmp;
}
public static int calcSize(BitmapFactory.Options options, int width, int height) {
final int uHeight = options.outHeight;
final int uWidth = options.outWidth;
int inSampleSize = 1;
if (uHeight > height || uWidth > width) {
if (uWidth > uHeight) {
inSampleSize = Math.round((float) uHeight / (float) height);
} else {
inSampleSize = Math.round((float) uWidth / (float) width);
}
}
return inSampleSize;
}
I'm writing a small game using a Custom SurfaceView. I wanted to put an AdMob view over the bottom portion of the SurfaceView, but after running the app for about six minutes, the app throws an error and then reboots the phone. I've replaced the AdMob view with a single button, and the error still occurs, so I assume the issues is caused by putting any view over the SurfaceView. If I remove any views from over the top of the SurfaceView, the game runs fine indefinitely.
I've tried using layouts other than RelativeLayout, but the problem persists. I'm wondering if anyone has run into this before, or has any other avenues to explore.
Custom SurfaceView:
public class AndroidFastRenderView extends SurfaceView implements Runnable
{
private static final String TAG = "AndroidFastRenderView";
AndroidGame game;
Bitmap framebuffer;
Thread renderThread = null;
SurfaceHolder holder;
volatile boolean running = false;
public AndroidFastRenderView(AndroidGame game, Bitmap framebuffer)
{
super(game);
this.game = game;
this.framebuffer = framebuffer;
this.holder = getHolder();
}
public AndroidFastRenderView(Context context)
{
super(context);
}
public void resume()
{
running = true;
renderThread = new Thread(this);
renderThread.start();
}
public void run()
{
Rect dstRect = new Rect(0,0,80,120);
long startTime = System.nanoTime();
while (running)
{
if(!holder.getSurface().isValid())
{
continue;
}
float deltaTime = (System.nanoTime() - startTime) / 1000000000.0f;
startTime = System.nanoTime();
game.getCurrentScreen().update(deltaTime);
game.getCurrentScreen().present(deltaTime);
Canvas canvas = null;
canvas = holder.lockCanvas();
if (canvas != null)
{
dstRect = canvas.getClipBounds();
canvas.drawBitmap(framebuffer, null, dstRect, null);
holder.unlockCanvasAndPost(canvas);
}
}
}
public void pause()
{
Log.e(TAG, "Paused");
running = false;
while(true)
{
try {
renderThread.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
}
Building the Interface in the main activity:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
int frameBufferWidth = isLandscape ? 120 : 80;
int frameBufferHeight = isLandscape ? 80 : 120;
Bitmap frameBuffer = Bitmap.createBitmap(frameBufferWidth, frameBufferHeight, Config.RGB_565);
float scaleX = (float) frameBufferWidth / getWindowManager().getDefaultDisplay().getWidth();
float scaleY = (float) frameBufferHeight / getWindowManager().getDefaultDisplay().getHeight();
renderView = new AndroidFastRenderView(this, frameBuffer);
graphics = new AndroidGraphics(getAssets(), frameBuffer);
fileIO = new AndroidFileIO(getAssets());
audio = new AndroidAudio(this);
input = new AndroidInput(this, renderView, scaleX, scaleY);
screen = this.getStartScreen();
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "GLGame");
if (useAds)
{
//adView = new AdView(this, AdSize.SMART_BANNER, AD_UNIT_ID);
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
//adView.setLayoutParams(params);
RelativeLayout layout = new RelativeLayout(this);
layout.addView(renderView);
//layout.addView(adView);
Button button = new Button(this);
button.setLayoutParams(params);
button.setText("Blargle");
layout.addView(button);
//adView.loadAd(new AdRequest());
setContentView(layout);
}
else
{
setContentView(renderView);
}
}
LogCat of the Error:
01-25 13:50:18.084: E/SurfaceTextureClient(10342): dequeueBuffer failed (Broken pipe)
01-25 13:50:18.084: E/SurfaceHolder(10342): Exception locking surface
01-25 13:50:18.084: E/SurfaceHolder(10342): java.lang.IllegalArgumentException
01-25 13:50:18.084: E/SurfaceHolder(10342): at android.view.Surface.lockCanvasNative(Native Method)
01-25 13:50:18.084: E/SurfaceHolder(10342): at android.view.Surface.lockCanvas(Surface.java:88)
01-25 13:50:18.084: E/SurfaceHolder(10342): at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:754)
01-25 13:50:18.084: E/SurfaceHolder(10342): at android.view.SurfaceView$4.lockCanvas(SurfaceView.java:734)
01-25 13:50:18.084: E/SurfaceHolder(10342): at com.somethingutility.games.framework.impl.AndroidFastRenderView.run(AndroidFastRenderView.java:60)
01-25 13:50:18.084: E/SurfaceHolder(10342): at java.lang.Thread.run(Thread.java:856)
This error repeats till the phone reboots.
Try to avoid displaying the AdView over the SurfaceView by specifically setting the renderRiew to be ABOVE the AdView:
params.addRule(RelativeLayout.ABOVE, renderView.getId());
You might need to give the renderView an ID if it doesn't have one.