1

How can I cancel a thread from another class fetching/refreshing location. I am able to cancel a thread from within the same class. But I am unable to do this across classes. Declaring the GPSThread static did not help. Can anyone please guide?

Class1:

public class GPSListener {
    /* Other instantiation code */
    Dialog busyDialog1 = new Dialog("Refreshing Location...",
                                    new String [] { "Cancel" },
                                    new int [] { Dialog.CANCEL},
                                    Dialog.CANCEL,
                                    Bitmap.getPredefinedBitmap(Bitmap.HOURGLASS))
    {
        public void fieldChanged(Field field1, int context1)
        {
            GPSHandler.requestStop();
            busyDialog1.cancel();
        }
    };

    public String refreshCoordinates() {
        String test = "nothing";
        if (GPSHandler.isStopRequested())
        {
            GPSHandler.stopRequested = false;
            return null;
        }
        GPSHandler.getInstance().setListener(this);
        GPSHandler.getInstance().requestLocationUpdates();
        if (GPSHandler.isStopRequested())
        {
            GPSHandler.stopRequested = false;
            return null;
        }
        busyDialog1.setEscapeEnabled(false);
        busyDialog1.show();
        return test;
    }

    public void onLocationReceived(Coordinates location) {
        lblLatitude.setText(Double.toString(location.getLatitude()));
        lblLongitude.setText(Double.toString(location.getLongitude()));
        busyDialog1.cancel();
    }
}

Class 2:

public class GPSHandler {
    private GPSThread _gpsThread;
    private Coordinates _location;
    private boolean _gotLocation;
    private GPSListener _listener;

    /** this class will be a Singleton, as the device only has one GPS system */
    private static GPSHandler _instance;

    /** @return the Singleton instance of the GPSHandler */
    public static GPSHandler getInstance() {
        if (_instance == null) {
            _instance = new GPSHandler();
        }
        return _instance;
    }

    public static boolean stopRequested = false;
    public synchronized static void requestStop() {
        stopRequested = true;
    }
    public synchronized static boolean isStopRequested() {
        return stopRequested;
    }

    /** not publicly accessible ... use getInstance() */
    private GPSHandler() {
    }

    /** call this to trigger a new location fix */
    public void requestLocationUpdates() {
        if (_gpsThread == null || !_gpsThread.isAlive()) {
            _gpsThread = new GPSThread();
            _gpsThread.start();
        }
    }

    public void setListener(GPSListener listener) {
        // only supports one listener this way
        _listener = listener;
    }

    private void setLocation(final Coordinates value) {
        _location = value;
        if (value.getLatitude() != 0.0 || value.getLongitude() != 0.0) {
            _gotLocation = true;
            if (_listener != null) {
                // this assumes listeners are UI listeners, and want callbacks on the UI thread:
                UiApplication.getUiApplication().invokeLater(new Runnable() {
                    public void run() {
                        _listener.onLocationReceived(value);
                    }
                });
            }
        }
    }

    private class GPSThread extends Thread {
        private void getLocationFromGoogle() {
            try {
                int cellID = GPRSInfo.getCellInfo().getCellId();
                int lac = GPRSInfo.getCellInfo().getLAC();
                String urlString2 = "http://www.google.com/glm/mmap";

                // Open a connection to Google Maps API
                ConnectionFactory connFact = new ConnectionFactory();
                ConnectionDescriptor connDesc;
                connDesc = connFact.getConnection(urlString2);
                HttpConnection httpConn2;
                httpConn2 = (HttpConnection)connDesc.getConnection();
                httpConn2.setRequestMethod("POST");

                // Write some custom data to Google Maps API
                OutputStream outputStream2 = httpConn2.openOutputStream();//getOutputStream();
                writeDataGoogleMaps(outputStream2, cellID, lac);

                // Get the response
                InputStream inputStream2 = httpConn2.openInputStream();//getInputStream();
                DataInputStream dataInputStream2 = new DataInputStream(inputStream2);

                // Interpret the response obtained
                dataInputStream2.readShort();
                dataInputStream2.readByte();
                final int code = dataInputStream2.readInt();

                UiApplication.getUiApplication().invokeLater(new Runnable() {
                    public void run() {
                        Dialog.alert(code + "");
                    }
                });
                if (code == 0) {
                    final double latitude = dataInputStream2.readInt() / 1000000D;
                    final double longitude = dataInputStream2.readInt() / 1000000D;
                    setLocation(new Coordinates(latitude, longitude, 0.0f));

                    UiApplication.getUiApplication().invokeLater(new Runnable() {
                        public void run() {
                            Dialog.alert(latitude+"-----"+longitude);
                        }
                    });

                    dataInputStream2.readInt();
                    dataInputStream2.readInt();
                    dataInputStream2.readUTF();
                } else {
                    System.out.println("Error obtaining Cell Id ");
                }
                outputStream2.close();
                inputStream2.close();
            } catch (Exception e) {
                System.out.println("Error: " + e.getMessage());
            }
        }

        private void tryGetLocationFromDevice() {
            _gotLocation = false;
            try {
                Criteria myCriteria = new Criteria();
                myCriteria.setCostAllowed(false);
                LocationProvider myLocationProvider = LocationProvider.getInstance(myCriteria);
                try {
                    Location myLocation = myLocationProvider.getLocation(300);
                    setLocation(myLocation.getQualifiedCoordinates());
                } catch ( InterruptedException iex ) {
                    System.out.println(iex.getMessage());
                } catch ( LocationException lex ) {
                    System.out.println(lex.getMessage());
                }
            } catch ( LocationException lex ) {
                System.out.println(lex.getMessage());
            }
            if (!_gotLocation) {
                getLocationFromGoogle();
            }
        }

        public void run() {
            int bbMapsHandle = CodeModuleManager.getModuleHandle("net_rim_bb_lbs"); // OS 4.5 - 6.0
            int bbMapsHandle60 = CodeModuleManager.getModuleHandle("net_rim_bb_maps"); // OS 6.0
            if (bbMapsHandle > 0 || bbMapsHandle60 > 0) {
                tryGetLocationFromDevice();
            } else {
                getLocationFromGoogle();
            }
        }
    }

    private void writeDataGoogleMaps(OutputStream out, int cellID, int lac) throws IOException {
        DataOutputStream dataOutputStream = new DataOutputStream(out);
        dataOutputStream.writeShort(21);
        dataOutputStream.writeLong(0);
        dataOutputStream.writeUTF("en");
        dataOutputStream.writeUTF("Android");
        dataOutputStream.writeUTF("1.0");
        dataOutputStream.writeUTF("Web");
        dataOutputStream.writeByte(27);
        dataOutputStream.writeInt(0);
        dataOutputStream.writeInt(0);
        dataOutputStream.writeInt(3);
        dataOutputStream.writeUTF("");
        dataOutputStream.writeInt(cellID);
        dataOutputStream.writeInt(lac);
        dataOutputStream.writeInt(0);
        dataOutputStream.writeInt(0);
        dataOutputStream.writeInt(0);
        dataOutputStream.writeInt(0);
        dataOutputStream.flush();
    }
} 
Nate
  • 31,017
  • 13
  • 83
  • 207
Sarah
  • 1,895
  • 2
  • 21
  • 39
  • 2
    You have posted rather a lot of code! :) You might want to consider cutting it down to only show the problem you're having. It makes it easier for other people to understand what you're asking for. – donturner Aug 16 '12 at 11:12

2 Answers2

2

Your GPSThread object is currently declared as a private inner class within GPSHandler. If you want to stop execution (or indeed do anything with it) from outside the scope of GPSHandler you will need to mark it as public. You will also need to provide some public mechanism (e.g. a stop() method) to cancel the thread execution.

The most common way of doing this is to have a boolean flag inside your thread (e.g shouldStop) which is checked within your main execution loop inside run() to see if it should stop. When the stop() method is called shouldStop is set to true and your Thread will stop.

Here's a good example: How to stop threads in Java?

Community
  • 1
  • 1
donturner
  • 17,867
  • 8
  • 59
  • 81
  • can you elaborate on this because calling the class public and playing around with boolean values to control start/stop of thread did not work for me. – Sarah Aug 22 '12 at 13:31
  • I have edited the above classes with the methods I added to the GPSHandler and called from the other class through the Dialog box. Can you please help? – Sarah Aug 23 '12 at 05:04
  • 1
    Answer updated. As mentioned before you should really consider cutting down your code to just that which is exhibiting the problem, for example the `writeDataGoogleMaps` method is redundant and only serves to detract from your problem. – donturner Aug 23 '12 at 10:18
1

There's two groups of changes you should make.

Change the Stop Requested Flag

First, remember that encapsulation is a good thing in Object-Oriented languages. The isStopRequested() method, or stopRequested variable of the GPSHandler should not be used outside of that class. Your UI's GPSListener should not attempt to use either of those. I would change your GPSHandler to use this:

private static boolean stopRequested = false;

public synchronized static void requestStop() {
   stopRequested = true;
}

private synchronized static boolean isStopRequested() {
   return stopRequested;
}

Only requestStop() should be public. It looks like you made stopRequested public to allow the GPSListener to reset it. If it needs resetting, let the class that owns that variable do the resetting. For example, in GPSHandler:

   /** call this to trigger a new location fix */
   public void requestLocationUpdates() {
       if (_gpsThread == null || !_gpsThread.isAlive()) {
          // reset this stop flag:
          stopRequested = false;
          _gpsThread = new GPSThread();
          _gpsThread.start(); 
       }
   }

requestLocationUpdates() is really the method that starts the thread, so it should be where stopRequested gets reset to false.

Also, another reason that you should not make stopRequested public and allow other classes to use it is that this is not generally thread-safe. One of the reasons to wrap stopRequested with the requestStop() and isStopRequested() methods is to add thread-safety. There's many ways to do that, but those two methods achieve thread-safety by being marked with the synchronized keyword.

Change How/Where You Check the Flag

After you make these fixes, you need to change where you check if a stop has been requested. You don't really want to check isStopRequested() in the refreshCoordinates() method. That method involves almost no work. Even though it starts the process of getting a location fix, that only starts a thread, but the actual work of getting the location is done on a background thread (your GPSThread). If requestStop() is called, it's very unlikely that it will be called in the middle of refreshCoordinates(), so that's not where you should check it.

Check isStopRequested() multiple times within the GPSHandler class's methods tryGetLocationFromDevice() and getLocationFromGoogle(). Those are the methods that perform slow processing. Those are the ones you might want to interrupt in the middle. So, something like this:

     private void getLocationFromGoogle() {
         try { 
            int cellID = GPRSInfo.getCellInfo().getCellId(); 
            int lac = GPRSInfo.getCellInfo().getLAC(); 

            String urlString2 = "http://www.google.com/glm/mmap"; 

            if (isStopRequested()) return;

            // Open a connection to Google Maps API  
            ConnectionFactory connFact = new ConnectionFactory(); 
            ConnectionDescriptor connDesc; 
            connDesc = connFact.getConnection(urlString2); 

            HttpConnection httpConn2; 
            httpConn2 = (HttpConnection)connDesc.getConnection(); 
            httpConn2.setRequestMethod("POST"); 

            // Write some custom data to Google Maps API  
            OutputStream outputStream2 = httpConn2.openOutputStream();//getOutputStream(); 
            writeDataGoogleMaps(outputStream2, cellID, lac); 

            if (isStopRequested()) return;

            // Get the response   
            InputStream inputStream2 = httpConn2.openInputStream();//getInputStream(); 
            DataInputStream dataInputStream2 = new DataInputStream(inputStream2); 

            // Interpret the response obtained  
            dataInputStream2.readShort(); 
            dataInputStream2.readByte(); 

            if (isStopRequested()) return;

            final int code = dataInputStream2.readInt(); 
            UiApplication.getUiApplication().invokeLater(new Runnable() {
               public void run() {
                  Dialog.alert(code + "");   
               }
            });                        

And in tryGetLocationFromDevice(), you could do this (make sure to add the member variable and new method below):

      private LocationProvider _locationProvider;  // must be a member variable!

      public void requestStop() {
         if (_locationProvider != null) {
             // this will interrupt the _locationProvider.getLocation(300) call
             _locationProvider.reset();
         }
      }

      private void tryGetLocationFromDevice() {
         _gotLocation = false;
         try {
            Criteria myCriteria = new Criteria(); 
            myCriteria.setCostAllowed(false); 
            _locationProvider = LocationProvider.getInstance(myCriteria); 

            try { 
               Location myLocation = _locationProvider.getLocation(300); 
               setLocation(myLocation.getQualifiedCoordinates()); 
            } catch ( InterruptedException iex ) { 
               // this may be caught if stop requested!!!!
               System.out.println(iex.getMessage());
            } catch ( LocationException lex ) { 
               System.out.println(lex.getMessage());
            } 
         } catch ( LocationException lex ) { 
            System.out.println(lex.getMessage());
         }

         if (!_gotLocation && !isStopRequested()) { 
            getLocationFromGoogle();
         } 
      }

Then, call the GPSThread.requestStop() method from the outer GPSHandler.requestStop() method:

public synchronized static void requestStop() {
   stopRequested = true;
   if (_gpsThread != null) {
       _gpsThread.requestStop();
   }
}
Nate
  • 31,017
  • 13
  • 83
  • 207
  • Thanks Nate for the above. I do not understand when you write "Then, call the GPSThread.requestStop() method from the outer GPSHandler.requestStop() method", what do you mean by outer GPSHandler.requestStop()? In your code you have two methods with the same name "requestStop()". This method is called within the method. Where is this to be added?The Dialog with the cancel button is in another class, so when the cancel is clicked, which method is called from that other class? Please explain. – Sarah Aug 26 '12 at 06:39
  • 1
    @Sarah, the `GPSThread` class is defined **inside** the `GPSHandler` class, so it's called an *inner* class. So, I'm referring to the `GPSHandler` class as the *outer* class. Both classes will now have a method called `requestStop()`. The version of `requestStop()` I show at the bottom of my answer (the one that has `synchronized` in front of it) is the one that goes into `GPSHandler`. That is the one that can be called from your UI code, when a cancel button is clicked. Inside that method, you see that it calls the `requestStop()` method of the `GPSThread` class. – Nate Aug 26 '12 at 18:30