0

I'm currently working on an application in Android. As a start, I want to draw on my Canvas using android.graphics.Path . Here's the code that I've worked upon (certain unused imports and commented code lines maybe present, as it's not my complete code, just the relevant part) :

import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;


public class HRCanvas extends Activity implements OnTouchListener{

    DrawPanel dp;
    private ArrayList<Path> pointsToDraw = new ArrayList<Path>();
    private Paint mPaint;
    Path path;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        dp = new DrawPanel(this);
        dp.setOnTouchListener(this);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        mPaint = new Paint();
        mPaint.setDither(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(30);

        FrameLayout fl = new FrameLayout(this);  
        fl.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  
        fl.addView(dp);  
        //fl.addView(tv1);  
        //fl.addView(b1);
        //fl.addView(b2);
        //fl.addView(b3);
        //fl.addView(ll);
        setContentView(fl);  
    //setContentView(dp);
    }

    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        dp.pause();
    }



    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        dp.resume();
    }



    public class DrawPanel extends SurfaceView implements Runnable{

        Thread t = null;
        SurfaceHolder holder;
        boolean isItOk = false ;

        public DrawPanel(Context context) {
            super(context);
            // TODO Auto-generated constructor stub
            holder = getHolder();
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub
            while( isItOk == true){

                if(!holder.getSurface().isValid()){
                    continue;
                }

                Canvas c = holder.lockCanvas();
                c.drawARGB(255, 0, 0, 0);
                onDraw(c);
                holder.unlockCanvasAndPost(c);
            }
        }

        @Override
        protected void onDraw(Canvas canvas) {
            // TODO Auto-generated method stub
            super.onDraw(canvas);
            for (Path path : pointsToDraw) {
                canvas.drawPath(path, mPaint);
            }
        }

        public void pause(){
            isItOk = false;
            while(true){
                try{
                    t.join();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                break;
            }
            t = null;
        }

        public void resume(){
            isItOk = true;  
            t = new Thread(this);
            t.start();

        }



    }


    @Override
    public boolean onTouch(View v, MotionEvent me) {
        // TODO Auto-generated method stub
        if(me.getAction() == MotionEvent.ACTION_DOWN){
            path = new Path();
            path.moveTo(me.getX(), me.getY());
            //path.lineTo(me.getX(), me.getY());
            pointsToDraw.add(path);
        }else if(me.getAction() == MotionEvent.ACTION_MOVE){
            path.lineTo(me.getX(), me.getY());
        }else if(me.getAction() == MotionEvent.ACTION_UP){
            //path.lineTo(me.getX(), me.getY());
        }

        return true;

    }

}

The code works fine for a little while, and then I get the error that the application has to shutdown( on my emulator). The following is the log content :

01-31 22:03:20.988: W/dalvikvm(760): threadid=11: thread exiting with uncaught exception (group=0x409c01f8)
01-31 22:03:20.997: E/AndroidRuntime(760): FATAL EXCEPTION: Thread-92
01-31 22:03:20.997: E/AndroidRuntime(760): java.util.ConcurrentModificationException
01-31 22:03:20.997: E/AndroidRuntime(760):  at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:569)
01-31 22:03:20.997: E/AndroidRuntime(760):  at learn.myandroidapp.hr.HRCanvas$DrawPanel.onDraw(HRCanvas.java:201)
01-31 22:03:20.997: E/AndroidRuntime(760):  at learn.myandroidapp.hr.HRCanvas$DrawPanel.run(HRCanvas.java:192)
01-31 22:03:20.997: E/AndroidRuntime(760):  at java.lang.Thread.run(Thread.java:856)

I know it has something to do with threads and arraylist, but I'm not able to solve the issue. Kindly help.

Kazekage Gaara
  • 14,972
  • 14
  • 61
  • 108

2 Answers2

1

It would appear that the onTouch() method is modifying the pointsToDraw list, while the onDraw() method is accessing it. You will need to add synchronization -- use the synchronized block around the access to the pointsToDraw variable, e.g. synchronized(this) { ... access pointsToDraw here ... }.

Consult this link for some examples: http://www.javamex.com/tutorials/synchronization_concurrency_synchronized1.shtml

Sasha Goldshtein
  • 3,499
  • 22
  • 35
  • so I need to put the `for` loop in the `onDraw()` method into the `synchonized` block? Right? `@Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); synchronized(this) { for (Path path : pointsToDraw) { canvas.drawPath(path, mPaint); } } }` – Kazekage Gaara Feb 20 '12 at 09:56
  • That's right, the loop and also the place where you add to the list (in `onTouch()`). – Sasha Goldshtein Feb 20 '12 at 10:34
  • the problem still persists. I've added the `synchronized` block wherever you have told me to. – Kazekage Gaara Feb 20 '12 at 10:53
  • 1
    Right -- because `this` is not the same in the two methods. One is a method of the inner class. Use a common object for synchronization. E.g., `synchronized (pointsToDraw)` or the external `this` object from the inner class. – Sasha Goldshtein Feb 20 '12 at 17:32
0

Generally it happens when one thread is busy in iterating the Collection and mean while other thread is trying to modify it. But i am not sure where the problem is there in your code

once check this link

java.util.ConcurrentModificationException in Non Multithreaded Program

Community
  • 1
  • 1
Sankar
  • 1,685
  • 2
  • 16
  • 27