androidgpsandroid-wifiandroid-twitter

android app - gps, wifi, and twitter


I've got an android application for a project at school. The goal of the app is to get the GPS location, do a scan of available wifi networks, and bundle that information into a character string to be tweeted (also by the app). The gps and wifi parts are written from scratch and the twitter code is some open sourced stuff one of my teammates found. None of us have any previous experience with android programming, so we've been learning what we need only fly. The app kind of works right now, but not well enough. It can usually get some tweets off, but it always eventually ends up getting an ANR. I think this is probably coming from the GPS or WIFI code, since that's the stuff we wrote ourselves. We wrote a separate app with just the twitter code to test it and that seemed to work fine, so I don't think that's causing the problem.

Could some android programmers out there with more experience take a look at some of this code and point out any issues they see, specifically what might be causing this app to ANR? Also if anybody is willing to suggest a better architecture/framework than what we currently have, I'd be interested. When the app works, we end up with a tweet getting sent that looks something like this:

ajd7v-34 U0b0ed38fc_____________________00b0ed39c4_____________________W0b0edf50c+44974893-093232387

this is the relevant code from our activity class...

    private Intent in;
public static TextView textView1;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_spectral_tweets);


    mConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);

    mProvider = new DefaultOAuthProvider(
            "http://api.twitter.com/oauth/request_token",
            "http://api.twitter.com/oauth/access_token",
            "http://api.twitter.com/oauth/authorize");

    prefs = PreferenceManager.getDefaultSharedPreferences(this);
    String token = prefs.getString("token", null);
    String tokenSecret = prefs.getString("tokenSecret", null);

    if (token != null && tokenSecret != null) {
        mConsumer.setTokenWithSecret(token, tokenSecret);
        oauthClient = new OAuthSignpostClient(CONSUMER_KEY,
                CONSUMER_SECRET, token, tokenSecret);
        twitter = new Twitter(TWITTER_USER, oauthClient);
    } else {
        Log.d(TAG, "onCreate. Not Authenticated Yet " );
        new OAuthAuthorizeTask().execute();
    }

    in = new Intent(this, BackgroundService.class);
    textView1 = (TextView) findViewById(R.id.textView1);
    changeText("On Create");
}

public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.activity_spectral_tweets, menu);
    return true;
}

/**
 * Changes textView1 to string msg
 */
public static void changeText(String msg) {
    textView1.setText(msg);
}

/**
 * tell the service to start displaying GPS/WIFI updates
 */
public void startMessages(View view) {
    startService(in);
}

/**
 * tell the service to stop displaying GPS/WIFI updates
 */
public void stopMessages(View view) {
    changeText("stopped");
    stopService(in);
}

/**
 * When the BACK key is pressed ask the user if they want to quit
 * if they do then stop the service and exit the program
 */
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if ((keyCode == KeyEvent.KEYCODE_BACK)) {
        @SuppressWarnings("unused")
        AlertDialog alertbox = new AlertDialog.Builder(this)
        .setMessage("Do you want to exit the application?")
        .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
            // stop the service and end the program
            public void onClick(DialogInterface arg0, int arg1) {
                stopService(in);
                finish();
            }
        })
        .setNegativeButton("No", new DialogInterface.OnClickListener() {
            // return to program
            public void onClick(DialogInterface arg0, int arg1) {}
        })

        .show();
    }
    return super.onKeyDown(keyCode, event);
}

this is the code from our service that handles the gps and wifi stuff...

public class BackgroundService extends Service implements LocationListener
{
private static Timer repeater = new Timer();
private static LocationManager lm;
private getInfoAndTweet getAndTweet = this.new getInfoAndTweet();

private static final int MIN_TIME_MILLISECONDS = 0;
private static final int MIN_DIST_METERS = 0;
private static final int frequency = 30 * 1000;
private double temp_long = 0.0;
private double temp_lat = 0.0;
private int temp_count = 0;
private boolean thread_running = false;


@Override
public IBinder onBind(Intent arg0) {
    return null;
}

public void onCreate()
{
    super.onCreate();
    lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
    lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_MILLISECONDS, MIN_DIST_METERS, this);
    Toast.makeText(getApplicationContext(), "Location display is on", Toast.LENGTH_SHORT).show();
    startService();
}

public void onDestroy()
{
    repeater.cancel();
    lm.removeUpdates(this);
    unregisterReceiver(getAndTweet.receiver);
    Toast.makeText(getApplicationContext(), "Location display is off", Toast.LENGTH_SHORT).show();
    temp_lat = 0;
    temp_long = 0;
    temp_count = 0;
}

private void startService()
{
    repeater.scheduleAtFixedRate(getAndTweet, 0, frequency);
}

/* this class will contain all of the GPS and WIFI classes so that none of that stuff clogs up the main thread */
private class getInfoAndTweet extends TimerTask
{
    WifiManager wifi;
    BroadcastReceiver receiver;

    DecimalFormat lat = new DecimalFormat("00.000000");
    DecimalFormat lon = new DecimalFormat("000.000000");
    String gps_info;
    String wifi_info;
    String final_string;
    private final String hashtag = "#ajd7v-34 ";
    int count = 0;

    private class WIFIscanner extends BroadcastReceiver
    {

        private final ArrayList<Integer> channel_numbers = new ArrayList<Integer> (Arrays.asList(0, 2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462));
        List <ScanResult> results;
        Map<Integer, String> levels = new HashMap<Integer, String>();
        String empty_channel = "__________";        // 10 spaces

        public WIFIscanner()
        {
            init_levels();
        }

            public void onReceive(Context context, Intent intent)
        {
            wifi_info = "";
            results = wifi.getScanResults();
            ScanResult sr;
            Iterator<ScanResult> it = results.iterator();
            ScanResult channel_info[] = new ScanResult[12];

            for (int i = 1; i < 12; i++)
            {
                channel_info[i] = null;
            }

            while (it.hasNext())
            {
                sr = it.next();
                int channel = channel_numbers.indexOf(Integer.valueOf(sr.frequency));

                if (channel_info[channel] == null)
                {
                    channel_info[channel] = sr;
                }
                else
                {
                    if (channel_info[channel].level < sr.level)
                    {
                        channel_info[channel] = sr;
                    }
                }
            }

            for (int i = 1; i < 12; i++)
            {
                if (channel_info[i] != null)
                {
                    wifi_info += (levels.get(channel_info[i].level) == null ? "0" : levels.get(channel_info[i].level))  + channel_info[i].BSSID.replace(":", "").substring(2, 11);
                }
                else
                {
                    wifi_info += empty_channel;
                }
            }

            final_string = hashtag + wifi_info + gps_info;
            if (temp_count != 0)
            {
                if(Twitter_Test_AppActivity.twitter != null) {
                    Twitter_Test_AppActivity.twitter.setStatus(final_string);
                    Twitter_Test_AppActivity.changeText("Auto Tweet Sent: " + count + "\t" + final_string);
                } else {
                    Twitter_Test_AppActivity.changeText("Tweet not sent");
                }
            }
            else
            {
                Twitter_Test_AppActivity.changeText(count + "\tno new GPS info");
            }
            thread_running = false;
        }

    }

    public void run()
    {
        thread_running = true;
        wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        if (receiver == null)
        {
            receiver = new WIFIscanner();
        }
        registerReceiver(receiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

        String latitude = lat.format(temp_lat / temp_count).replace(".",  "");
        String longitude = lon.format(temp_long / temp_count).replace(".",  "");
        gps_info = ((temp_lat / temp_count) > 0 ? "+" : "") + latitude + ((temp_long / temp_count) > 0 ? "+" : "") + longitude;

        wifi.startScan();
        count++;
    }
}

public void onLocationChanged(Location location) {
    if (thread_running)
    {
        temp_lat += location.getLatitude();
        temp_long += location.getLongitude();
        temp_count ++;
    }
}

public void onProviderDisabled(String provider) {
    Toast.makeText(getApplicationContext(), "GPS disabled", Toast.LENGTH_SHORT).show();

}

public void onProviderEnabled(String provider) {
    Toast.makeText(getApplicationContext(), "GPS enabled", Toast.LENGTH_SHORT).show();

}

public void onStatusChanged(String provider, int status, Bundle extras) {}

}

EDIT as I was thinking about this more, it occurred to me that the twitter code running on the main thread could be causing my ANR as well. the network at school isn't the greatest and my laptop even has trouble getting on sometimes (not my laptop's fault, it's fine everywhere else). could a slow or poor network connection cause my main thread to get hung up when trying to send the tweet, causing the phone to ANR my app?


Solution

  • First of all TimerTask is not the best place to store your data. Use your TimerTask only to periodically execute some methods from your Service.

    Secondly, it's better to make all inner classes of all Context related components like Activity or Service as static classes. This will protect your code from memory leaks.

    Also you it's better to register BroadcastRreceiver just once and don't do this in every call of timer task.

    I truly recommend you to read something, for example book of Reto Meier "Android application development".