2

I am getting the following error on devices Galaxy Nexus (maguro),Nexus 4 (mako),Nexus 7 (flo), Xperia Z (C6603) but my device Note2 4.1.1 works fine. What I am doing is using a Visualizer with my sound file to show the user cool graph-like feedback from their audio files.

I am using com.pheelicks.visualizer in my app and it works fine on my device but it has been crashing for some of my users. This is hard for me to test since I don't have a new device, but if someone can confirm why I am getting the error I would greatly appreciate it! I am note sure if it has anything to do with it, but I also start with the view's visibility as view.GONE and then set it to visible when the time is right, and then start an animation. I will include this code below just in case this is what is the problem.

java.lang.IllegalArgumentException: width and height must be > 0
at android.graphics.Bitmap.createBitmap(Bitmap.java:695)
at android.graphics.Bitmap.createBitmap(Bitmap.java:674)
at android.graphics.Bitmap.createBitmap(Bitmap.java:641)
at com.pheelicks.visualizer.VisualizerView.onDraw(VisualizerView.java:193)
at android.view.View.draw(View.java:13944)
at android.view.View.getDisplayList(View.java:12838)
at android.view.View.getDisplayList(View.java:12880)
at android.view.View.draw(View.java:13657)
at android.view.ViewGroup.drawChild(ViewGroup.java:3083)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:2920)
at android.view.View.draw(View.java:13947)
at android.view.View.getDisplayList(View.java:12838)
at android.view.View.getDisplayList(View.java:12880)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3057)
at android.view.View.getDisplayList(View.java:12775)
at android.view.View.getDisplayList(View.java:12880)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3057)
at android.view.View.getDisplayList(View.java:12775)
at android.view.View.getDisplayList(View.java:12880)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3057)
at android.view.View.getDisplayList(View.java:12775)
at android.view.View.getDisplayList(View.java:12880)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3057)
at android.view.View.getDisplayList(View.java:12775)
at android.view.View.getDisplayList(View.java:12880)
at android.view.HardwareRenderer$GlRenderer.buildDisplayList(HardwareRenderer.java:1411)
at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1359)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:2367)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2239)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5481)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
at android.view.Choreographer.doCallbacks(Choreographer.java:562)
at android.view.Choreographer.doFrame(Choreographer.java:532)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)

For Reference here is the custome VisualizerView

 public class VisualizerView extends View {
 private static final String TAG = "VisualizerView";
 private boolean isLinked = false;
 private byte[] mBytes;
 private byte[] mFFTBytes;
 private Rect mRect = new Rect();
 private Visualizer mVisualizer;

 private Set<Renderer> mRenderers;

 private Paint mFlashPaint = new Paint();
 private Paint mFadePaint = new Paint();

 public VisualizerView(Context context, AttributeSet attrs, int defStyle)
 {
  super(context, attrs);
  init();
 }

 public VisualizerView(Context context, AttributeSet attrs)
 {
   this(context, attrs, 0);
 }

 public VisualizerView(Context context)
 {
   this(context, null, 0);
 }

 private void init() {
   mBytes = null;
   mFFTBytes = null;

   mFlashPaint.setColor(Color.argb(122, 255, 255, 255));
   mFadePaint.setColor(Color.argb(150, 255, 255, 255)); // Adjust alpha to change how quickly the image fades
   mFadePaint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));

   mRenderers = new HashSet<Renderer>();
 }
 /**
  * Links the visualizer to a player
  * @param player - MediaPlayer instance to link to
  */
 public void link(MediaPlayer player)
 {
   if(player == null)
   {
    throw new NullPointerException("Cannot link to null MediaPlayer");
   }

   // Create the Visualizer object and attach it to our media player.
   mVisualizer = new Visualizer(player.getAudioSessionId());
   mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);

   // Pass through Visualizer data to VisualizerView
   Visualizer.OnDataCaptureListener captureListener = new Visualizer.OnDataCaptureListener()
   {
     @Override
     public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
         int samplingRate)
     {
       updateVisualizer(bytes);
     }

     @Override
     public void onFftDataCapture(Visualizer visualizer, byte[] bytes,
      int samplingRate)
     {
      updateVisualizerFFT(bytes);
     }
   };

   mVisualizer.setDataCaptureListener(captureListener,
       Visualizer.getMaxCaptureRate() / 2, true, true);

    // Enabled Visualizer and disable when we're done with the stream
   mVisualizer.setEnabled(true);
   player.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
   {
     @Override
     public void onCompletion(MediaPlayer mediaPlayer)
     {
      mVisualizer.setEnabled(false);
    }
   });

   this.setLinked(true);
  }

 public void addRenderer(Renderer renderer)
 {
   if(renderer != null)
   {
    mRenderers.add(renderer);
   }
 }

 public void clearRenderers()
 {
   mRenderers.clear();
 }

 /**
  * Call to release the resources used by VisualizerView. Like with the
  * MediaPlayer it is good practice to call this method
  */
 public void release()
 {
   mVisualizer.release();
   this.setLinked(false);
 }
 /**
   * Pass data to the visualizer. Typically this will be obtained from the
   * Android Visualizer.OnDataCaptureListener call back. See
   * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture }
   * @param bytes
   */
 public void updateVisualizer(byte[] bytes) {
   mBytes = bytes;
   invalidate();
 }

  /**
  * Pass FFT data to the visualizer. Typically this will be obtained from the
  * Android Visualizer.OnDataCaptureListener call back. See
  * {@link Visualizer.OnDataCaptureListener#onFftDataCapture }
  * @param bytes
  */
  public void updateVisualizerFFT(byte[] bytes) {
    mFFTBytes = bytes;
    invalidate();
   }

    boolean mFlash = false;

  /**
  * Call this to make the visualizer flash. Useful for flashing at the start
  * of a song/loop etc...
  */
  public void flash() {
    mFlash = true;
    invalidate();
  }

    Bitmap mCanvasBitmap;
     Canvas mCanvas;


  @Override
  protected void onDraw(Canvas canvas) {
   super.onDraw(canvas);

   // Create canvas once we're ready to draw
   mRect.set(0, 0, getWidth(), getHeight());  

   if(mCanvasBitmap == null)
   {
     mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888); // THIS IS WHERE THE ERROR IS OCCURRING
   }
   if(mCanvas == null)
   {
     mCanvas = new Canvas(mCanvasBitmap);
   }

   if (mBytes != null) {
     // Render all audio renderers
     AudioData audioData = new AudioData(mBytes);
     for(Renderer r : mRenderers)
     {
       r.render(mCanvas, audioData, mRect);
     }
   }

   if (mFFTBytes != null) {
     // Render all FFT renderers
     FFTData fftData = new FFTData(mFFTBytes);
     for(Renderer r : mRenderers)
     {
       r.render(mCanvas, fftData, mRect);
     }
   }

   // Fade out old contents
   mCanvas.drawPaint(mFadePaint);

   if(mFlash)
   {
     mFlash = false;
     mCanvas.drawPaint(mFlashPaint);
   }

   canvas.drawBitmap(mCanvasBitmap, new Matrix(), null);
 }

 public boolean isLinked() {
  return isLinked;
  }

 private void setLinked(boolean isLinked) {
this.isLinked = isLinked;
  }
}

OnCreate in Activity (the visualizer_container has the visualizerView inside it)

this.visualizer_container = (RelativeLayout) findViewById(R.id.visualizer_layout);
this.visualizer_container.setVisibility(View.GONE);

Later in activity

sAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_bottom);
            sAnimation.setInterpolator(new AccelerateInterpolator());
            sAnimation.setStartOffset(400);
            sAnimation.setDuration(1000);


            this.mVisualizerView.setAnimation(sAnimation);
Jordan Hochstetler
  • 1,308
  • 3
  • 17
  • 28
  • My speculation is that onDraw() is called before onMeasure(), so essentially width/height will be zero. Maybe its a simple case of checking whether they are greater than zero and then proceeding with your draw function. One way would also be to override onMeasure to ensure you view is measured correctly and then call invalidate(). Though I'm not sure how feasible it is. – Sun Dec 05 '13 at 12:57
  • This should not be possible given that the draw cycle contains three phases: 1) measure, 2) layout (calculates position) and 3) draw. – Jeffrey Klardie Dec 05 '13 at 13:02
  • Right, and what I don't understand is how this works on my phone but it's not working on other peoples!?! – Jordan Hochstetler Dec 05 '13 at 13:05
  • Manufacturers of phones (such as Samsung, Sony, etc) are able to modify Android to add stuff (it's open source after all). Sometimes (read quite regularly if you test different devices daily), this results in small differences in behavior. – Jeffrey Klardie Dec 05 '13 at 13:07

2 Answers2

1

Your comment in the code is wrong, this is the line that fails (in the stacktrace you can see that the next call is Bitmap.createBitmap()):

mCanvasBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888);

The problem is that either canvas.getWidth() or canvas.getHeight() , or both, return 0. That is not allowed. In turn, that is caused by the line you indicate in the comment, in which getWidth()/getHeight() returns 0.

I would add a log statements that prints these, so you can see if it only happens the first time, or always. If it only happens the first time, you might be able to ignore it (e.g. check if width and height > 0).

Update: considering you eventually have to draw to the canvas, why not use the canvas' width and height?

mRect.set(0, 0, canvas.getWidth(), canvas.getHeight());  
Jeffrey Klardie
  • 3,020
  • 1
  • 18
  • 23
  • Fixed the comment. Okay, thanks for pointing that out. I'll add the logs and report back – Jordan Hochstetler Dec 05 '13 at 13:12
  • I read your update... I believe this is not the source of the problem. I think it is that my VisualerView is GONE at first and then later is VISIBLE. Does this screw up the measuring? Because when I take that out it works fine. It's not the behavior I want, because I don't want the View to be visible at first, but it does get rid of the error. – Jordan Hochstetler Dec 05 '13 at 13:28
  • Yes, that does screw up measuring. Views that are set to GONE will not be part of the draw cycle at all (no measuring, no layout pass, and no draw). You can set the visibility to `INVISIBLE`, if that does not mess up your layout. That way, the measure and layout (positioning) calculations are done, and only draw is skipped. – Jeffrey Klardie Dec 05 '13 at 13:31
  • 1
    Actually I think I spotted the error! I was setting my mVisualView to VISIBLE, **BUT** I was not setting my visual_container (which holds the mVisualView) to VISIBLE. This was making the child view mVisualView also still GONE, because the parent view wasn't VISIBLE... If that all makes sense. Cheers! You can update your answer to point this out and I will +1 you – Jordan Hochstetler Dec 05 '13 at 13:38
  • You fixed it yourself, good :) You can add your own answer, and accept that! That way others can find it too. – Jeffrey Klardie Dec 05 '13 at 13:41
  • 2
    This seems like it would be helpful to other people. If you get the time would you mind writing an answer and accepting it to make the solution more obvious to googlers. ;) – Troy Patrick Mar 18 '14 at 02:01
-3

Add Crashlytics to your code to be able to follow the error on users' device. http://www.crashlytics.com/blog/its-finally-here-announcing-crashlytics-for-android/

Hamid
  • 355
  • 3
  • 10
  • 2
    This is surely not even near to an answer for this question. Give suggestions in comments only if you **must**. – Shubham A. Nov 04 '15 at 05:33