views:

93

answers:

1

I have a database filled with records in the following format: . What I want my application to do is select records from an external database, and display those records on the phone screen using a SurfaceView.

Currently, I have an Activity, and a Service responsible for the record-gathering portion of the application. The Activity passes an intent to the Service, and the Service responds by returning the records that need to be displayed. The records are stored in my program as instances of the Data class, and merely have the screen-coordinates of where the element should be drawn in the View (I am just drawing a circle for every record in the DB).

For the sake of brevity, I won't include the service but I will include a skeleton of the Activity class and the Data that I wish to display.

public class Data{
    private int x;
    private int y;
    private int id;
    private int shape;

    /*
    * Getters and setters for class properties
    */
}

public class Displayer extends Activity {
    int ht;
    int wt;
    dataReceiver dreceiver; 
    public static Map<String, Data> Info; 
    private LinearLayout linear; 
    private static final int RADIUS = 20;
    Panel panel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        panel=new Panel(this);
        setContentView(panel);

        Intent scv = new Intent(this, DataService.class);
        startService(scv);
    }
    /*
    * Various other methods to take care of onDestroy, onPause, etc.
    */

    public class dataReceiver extends BroadcastReceiver {
        //Get new data from intents sent by the Service and store it in Info variable

        //Update the Testing Map data-structure in the Panel SurfaceView with new records
        panel.setData(Info);

    }
}

The problem that I am having pertains to the SurfaceView. I realize that many people are going to suggest that I use just a regular View, but my application involves a lot of elements, so a SurfaceView would be much more suitable for my needs. Below is a skeleton of my SurfaceView class that contains a nested class to manage the threads.

public class Panel extends SurfaceView implements SurfaceHolder.Callback {
    private PanelThread _thread;

    private Paint circlepaint;
    private Paint textpaint;

    private static int CircleColor = Color.YELLOW;
    private static int TextColor = Color.RED;
    private static Map<String, Data> Testing;

    public Panel(Context context, Map<String, Data> entries) {
        super(context);

        getHolder().addCallback(this);
        _thread = new PanelThread(getHolder(), this);
        setFocusable(true);

        textpaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textpaint.setStyle(Style.FILL_AND_STROKE);
        textpaint.setColor(TextColor);

        Testing = new HashMap<String, Data>();
        // Log.d("TestingSize",String.valueOf(Testing.size()));
        if (!(Testing.isEmpty())) {
            Testing.clear();
        }

        for (Map.Entry<String, Data> e : entries.entrySet()) {
            String keyvalue = e.getKey();
            Data v = e.getValue();
            Panel.Testing.put(keyvalue, v);
        }

    }

    public Panel(Context context) {
        super(context);
        getHolder().addCallback(this);
        _thread = new PanelThread(getHolder(), this);
        setFocusable(true);
        textpaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        textpaint.setStyle(Style.FILL_AND_STROKE);
        textpaint.setColor(TextColor);
        Testing = new HashMap<String, Victims>();

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // TODO Auto-generated method stub
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        _thread.setRunning(true);
        _thread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        /* we have to tell thread to shut down 
        * & wait for it to finish, 
        * or else it might touch the Surface 
        * after we return and explode
        */

        boolean retry = true;
        _thread.setRunning(false);
        while (retry) {
            try {
                _thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }   
    /*If new records are received 
    * from the service, they can be updated 
    * using this method
    */
    public void setData(Map<String,Data>Info){
        Testing=Info;
    }

    /*
    * Iterate over all contents of Testing List and display them to the screen
    */
    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (!(Testing.isEmpty())) {
            for (Map.Entry<String, Victims> e : Testing.entrySet()) {
                Data d = e.getValue();      
                canvas.drawCircle(d.getX(), d.getY(), 10, circlepaint);
            }
        }

        canvas.save();
        canvas.restore();
    }

    /*
    * Nested class to manage the threads for the SurfaceView
    */
    class PanelThread extends Thread {
        private SurfaceHolder _surfaceHolder;
        private Panel _panel;
        private boolean _run = false;

        public PanelThread(SurfaceHolder surfaceHolder, Panel panel) {
            _surfaceHolder = surfaceHolder;
            _panel = panel;
        }

        public void setRunning(boolean run) {
            _run = run;
        }

        public SurfaceHolder getSurfaceHolder() {
            return _surfaceHolder;
        }

        @Override
        public void run() {
            Canvas c;
            while (_run) {
                c = null;
                try {
                    c = _surfaceHolder.lockCanvas(null);
                    synchronized (_surfaceHolder) {
                        _panel.onDraw(c);
                    }
                } finally {
                    // do this in a finally so that if an exception is thrown
                    // during the above, we don't leave the Surface in an
                    // inconsistent state
                    if (c != null) {
                        _surfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }
    }//end of PanelThread

}//end of Panel

The problem that I'm having is that once I make the initial call to the Panel class, I'm going to be getting new records from the service, and consequently, my Info Map data-structure is going to get updated. However, my Panel class just gets stuck in a loop and never receives any new Data objects. All examples of SurfaceViews I've found have involved updating them methods within the SurfaceView class itself (e.g. touching the screen and creating a new image, etc.) Sadly, I'm stumped on this particular problem. Is there a better approach for designing my Activity/SurfaceView interaction? Is an additional View required?

+1  A: 

From a quick look at your code I think the problem is that you are trying to update a data structure from the UI thread while acting on it in the PanelThread. It also looks like your Activity class and Panel are also out of sync - I don't see the setData method in your Panel class, but I assume it updates the Testing static member variable.

My suggestions:

1 - Testing should not be static - it's fundamentally associated with this instance of the class. 2 - Use Synchronize when touching the data (Testing). This should be done for both setting and reading the data. 3 - Put a sleep or a wait call in your run loop - this gives time for the activity UI thread to update Testing. Also saves battery and allows you to pick a frame rate for updates.

Colin Stewart
@Colin Stewart Thanks for the reply. I added the setData method to my question. Regarding the use of 'synchronize', should I wrap any updating of the Testing data-structure in a synchronize block.e.g. synchronize(this){...}
rohanbk
synchronized(this) would lock on the whole object and ensure that access to instance fields are carried out correctly (see the point about making Testing an instance rather than a class variable). You need to wrap both reading and writing so that you ensure that the Testing field can not be updated while you are trying to read from it. If you keep Testing as a static field you will need to synchronize on Panel.Class.
Colin Stewart
@Colin Stewart Thanks for the advice. The data points that I want to draw are now appearing on my SurfaceView. However, old images are retained on the SurfaceView, making each of the drawn images have a 'trail'. Do you know how I can take care of this?
rohanbk
When using SurfaceView you have to do all of the drawing - including the background. The simplest thing to do is to call Canvas.drawColor() with whatever background colour you are using before drawing the new data points.
Colin Stewart