Mck
Mck

Reputation: 149

Can someone tell me what is wrong with my android app

I have written an android app which basically allows me to keep track of times and address of GPS coordinates.

I have 3 Lists each corresponding to Lyft, Uber and Other.

But I believe my app starts slowing down my Smart Phone (Samsung Galaxy S7 Edge, with Android O)

can someone look at my code and tell me why is it slowing my smart phone.

My assumption is that, possibly thread synchronization issue.

Attached is my code

1) MainActivity.java

package com.milind.myapp.gpstrackingservice;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.PowerManager;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements AddressListener
{
    private static final String TAG = MainActivity.class.getSimpleName();
    private PowerManager.WakeLock wakeLock;
    private TextView labelAddress;
    private TextView multiTextLyft;
    private TextView multiTextUber;
    private TextView multiTextOther;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        labelAddress = findViewById(R.id.label_address);
        multiTextLyft = findViewById(R.id.multi_text_lyft);
        multiTextUber = findViewById(R.id.multi_text_uber);
        multiTextOther = findViewById(R.id.multi_text_other);
        if (MyService.isServiceStarted())
        {
            MyService.getInstance().load(getSharedPrefs());
            refreshAllViews();
        }

        PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "myapp:My Lock");
    }

    @Override
    protected void onStart()
    {
        super.onStart();
        ActivityCompat.requestPermissions(this, new String[]
                {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WAKE_LOCK}, 1);

        if (!MyService.isServiceStarted())
        {
            Intent intent = new Intent(this, MyService.class);
            intent.setAction(MyService.ACTION_START_SERVICE);

            if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            {
                startForegroundService(intent);
            }
            else
            {
                startService(intent);
            }
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus)
    {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus)
        {
            if (MyService.isServiceStarted())
            {
                MyService.getInstance().registerAddressListener(this);
            }
            wakeLock.acquire();
        }
        else
        {
            if (MyService.isServiceStarted())
            {
                MyService.getInstance().unregisterAddressListener(this);
            }
            wakeLock.release();
        }
    }


    public void onRequestPermissionsResult(int requestCode, String permissions[],
                                           int[] grantResults)
    {
        switch (requestCode)
        {
            case 1:
            {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED)
                {

                }
                else
                {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                }
                return;
            }
            // other 'case' lines to check for other
            // permissions this app might request
        }
    }

    @Override
    public void onLocationChanged(Location loc)
    {
        final String address = MyService.getAddress(this, loc);
        final CharSequence text = labelAddress.getText();
        if (text.toString().equals(address))
        {
            return;
        }

        MyService.getInstance().load(getSharedPrefs());
        labelAddress.setText(address);

        refreshAllViews();

        new Tone().play(880);
    }

    private void refreshAllViews()
    {
        runOnUiThread(new Runnable()
        {
            public void run()
            {
                refereshEditTextLyft();
                refereshEditTextUber();
                refereshEditTextOther();
            }
        });
    }

    private void refereshEditTextLyft()
    {
        multiTextLyft.setText(MyService.getInstance().getLyftAddresses());
    }

    private void refereshEditTextUber()
    {
        multiTextUber.setText(MyService.getInstance().getUberAddresses());
    }

    private void refereshEditTextOther()
    {
        multiTextOther.setText(MyService.getInstance().getOtherAddresses());
    }

    private SharedPreferences getSharedPrefs()
    {
        return getSharedPreferences("name", MODE_PRIVATE);
    }

    public void onLyftButtonClicked(View view)
    {
        MyService.getInstance().addLyftAddress(labelAddress.getText());
        new Tone().play(440);
        refereshEditTextLyft();

        MyService.getInstance().save(getSharedPrefs());
    }

    public void onUberButtonClicked(View view)
    {
        MyService.getInstance().addUberAddress(labelAddress.getText());
        new Tone().play(440);
        refereshEditTextUber();

        MyService.getInstance().save(getSharedPrefs());
    }

    public void onOtherButtonClicked(View view)
    {
        MyService.getInstance().addOtherAddress(labelAddress.getText());
        new Tone().play(440);
        refereshEditTextOther();

        MyService.getInstance().save(getSharedPrefs());
    }

    public void onClearButtonClicked(View view)
    {
        if (MyService.isServiceStarted())
        {
            SharedPreferences sharedPreferences = getSharedPrefs();
            SharedPreferences.Editor editor = sharedPreferences.edit();
            editor.clear();
            editor.commit();
            MyService.getInstance().clear();
            refereshEditTextLyft();
            refereshEditTextUber();
            refereshEditTextOther();
        }
    }

    public void onDelLyftButtonClicked(View view)
    {
        MyService.getInstance().delLyftEntry();
        new Tone().play(440);
        refereshEditTextLyft();

        MyService.getInstance().save(getSharedPrefs());
    }

    public void onDelUberButtonClicked(View view)
    {
        MyService.getInstance().delUberEntry();
        new Tone().play(440);
        refereshEditTextUber();

        MyService.getInstance().save(getSharedPrefs());
    }

    public void onDelOtherButtonClicked(View view)
    {
        MyService.getInstance().delOtherEntry();
        new Tone().play(440);
        refereshEditTextOther();

        MyService.getInstance().save(getSharedPrefs());
    }

    @Override
    protected void onRestart()
    {
        super.onRestart();
    }

    @Override
    public void onActionModeFinished(ActionMode mode)
    {
        super.onActionModeFinished(mode);
    }

    @Override
    public void onBackPressed()
    {
        super.onBackPressed();
    }
}

2) The MyService.java

package com.milind.myapp.gpstrackingservice;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class MyService extends Service implements LocationListener
{
    private static final String TAG = MyService.class.getSimpleName();
    public static final String ACTION_START_SERVICE = "ACTION_START_SERVICE";
    public static final String ACTION_STOP_SERVICE = "ACTION_STOP_SERVICE";
    public static final String ACTION_UBER = "ACTION_UBER";
    public static final String ACTION_LYFT = "ACTION_UBER";
    public static final String ACTION_END = "ACTION_END";
    public static final String LYFT_PREFIX = "Lyft";
    public static final String OTHER_PREFIX = "Other";
    public static final String UBER_PREFIX = "Uber";

    private static MyService mInstance = null;

    private List<AddressListener> listeners = new ArrayList<>();
    private List<AddressPoint> lyftAddresses = new ArrayList<>();
    private List<AddressPoint> uberAddresses = new ArrayList<>();
    private List<AddressPoint> otherAddresses = new ArrayList<>();
    private Location mLastLocation;

    public MyService()
    {
        super();
        Log.d(TAG, "MyService(): constructor called");
    }

    public static boolean isServiceStarted()
    {
        Log.d(TAG, "isServiceStarted()");
        return mInstance != null;
    }

    public static final MyService getInstance()
    {
        return mInstance;
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        Log.d(TAG, "onBind(Intent intent)");
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        Log.d(TAG, "onStartCommand(Intent, flags, startId) : " + hashCode());
        Log.d(TAG, "onStartCommand(...) intent=" + intent + "=" + ", flags=" + flags + ", startId=" + startId);
        Log.d(TAG, "onStartCommand(...) isServiceStarted=" + isServiceStarted());
        String action = null;
        if (intent != null)
        {
            Log.d(TAG, intent.toString());
            action = intent.getAction();
        }
        else
        {
            Log.d(TAG, "onStartCommand(...): early return");
            return super.onStartCommand(intent, flags, startId);
        }

        if (isServiceStarted() == false && action == ACTION_START_SERVICE)
        {
            Log.d(TAG, "onStartCommand(...): Service starting=" + startId);
            //startForegroundServivceNotification()
            startRunningInForeground();
            requestLocationUpdates();
            mInstance = this;
            Log.d(TAG, "onStartCommand(...): Service started=" + startId);
        }
        else if (isServiceStarted() == true && action == ACTION_STOP_SERVICE)
        {
            Log.d(TAG, "onStartCommand(...): Service stopping=" + startId);
            stopLocationUpdates();
            stopSelf();
            Log.d(TAG, "onStartCommand(...): Service stop requested" + startId);
            mInstance = null;
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy()
    {
        Log.d(TAG, "onDestroy(): Service destroyed");
        super.onDestroy();
        mInstance = null;
    }

    private void stopLocationUpdates()
    {
        Log.d(TAG, "stopLocationUpdates()");
        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        locationManager.removeUpdates(this);
    }

    private void requestLocationUpdates()
    {
        Log.d(TAG, "requestLocationUpdates()");
        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        try
        {
            locationManager.requestLocationUpdates(
                    LocationManager.GPS_PROVIDER, 1500, 0, this);
            Log.w(TAG, "requestLocationUpdates(): ended gracefully");
        }
        catch (SecurityException ex)
        {
            Log.w(TAG, "requestLocationUpdates(): Exception");
            ex.printStackTrace();
        }
    }

    @Override
    public void onLocationChanged(Location location)
    {
        Log.v(TAG, "onLocationChanged(Location location): started, location=" + location.toString());
        dispatchLocationChange(location);
        mLastLocation = location;
        Log.v(TAG, "onLocationChanged: completed");
    }

    private void dispatchLocationChange(Location loc)
    {
        Log.v(TAG, "dispatchLocationChange(Location)");
        for (AddressListener listener : listeners)
        {
            listener.onLocationChanged(loc);
        }
    }

    public static String getAddress(Context context, Location loc)
    {
        Log.v(TAG, "getAddress(Location loc) started");
        List<Address> addresses;
        Geocoder gcd = new Geocoder(context, Locale.getDefault());
        try
        {
            addresses = gcd.getFromLocation(loc.getLatitude(),
                    loc.getLongitude(), 1);
            String strReturnAddress = "";

            if (addresses != null && addresses.size() > 0)
            {
                final Address address = addresses.get(0);
                Log.d(TAG, address.toString());
                String addressLines = "";

                Log.v(TAG, "Locale: " + address.getLocale());

                for (int i = 0; i <= address.getMaxAddressLineIndex(); ++i)
                {
                    Log.v(TAG, "AddressLine " + i + ": " + address.getAddressLine(i));
                    addressLines += address.getAddressLine(i) + ", ";
                    Log.v(TAG, "addressLines:" + addressLines);
                }

                String strAddress =
                        addressLines
                        ;
                Log.v(TAG, "strAddress:" + strAddress);

                strReturnAddress = strAddress.substring(0, strAddress.length() - 2);
                Log.v(TAG, "strReturnAddress:" + strReturnAddress);
            }
            Log.d(TAG, "getAddress(Location loc) completed with return=" + strReturnAddress);
            return strReturnAddress;
        }
        catch (IOException e)
        {
            e.printStackTrace();
            Log.d(TAG, "Exception", e);
        }

        Log.d(TAG, "getAddress(Location loc) completed with return=null");
        return "";
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras)
    {
        Log.d(TAG, "onStatusChanged(provider, status, extras): status=" + status + ", extras=" + extras);

    }

    @Override
    public void onProviderEnabled(String provider)
    {
        Log.d(TAG, "onProviderEnabled(provider) ");
    }

    @Override
    public void onProviderDisabled(String provider)
    {
        Log.d(TAG, "onProviderDisabled(provider) ");
        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    private static void logGetProperties(final String tag, final Object obj)
    {
        Log.v(tag, "logGetProperties(...)");
        Class cls = obj.getClass();

        Method[] methods = cls.getMethods();
        Log.v(tag, "methods.length = " + methods.length);

        for (Method method : methods)
        {
            String methodName = method.getName();
            Log.v(tag, "methodName = " + methodName);

            if (methodName.startsWith("get")
                    && (method.getParameters() == null || method.getParameters().length == 0)
                    && method.getReturnType() != Void.class)
            {
                try
                {
                    Log.v(tag, methodName + " = " + method.invoke(obj, new Object[0]));
                }
                catch (Exception ex)
                {
                    Log.e(tag, methodName + " Failed (exception)");
                }
            }
        }
    }

    public AddressListener registerAddressListener(AddressListener listener)
    {
        Log.d(TAG, "registerAddressListener(AddressListener)");
        if (!listeners.contains(listener))
        {
            listeners.add(listener);
        }
        return listener;
    }

    public AddressListener unregisterAddressListener(AddressListener listener)
    {
        Log.d(TAG, "unregisterAddressListener(AddressListener)");
        if (listeners.contains(listener))
        {
            listeners.remove(listener);
            Log.d(TAG, "unregisterAddressListener(AddressListener): Listener removed");
            return listener;
        }
        Log.d(TAG, "unregisterAddressListener(AddressListener): Listener not found");
        return null;
    }

    public void addLyftAddress(CharSequence text)
    {
        Log.d(TAG, "addLyftAddress(CharSequence text): text: " + text);
        lyftAddresses.add(new AddressPoint(System.currentTimeMillis(), text));
    }

    public void addUberAddress(CharSequence text)
    {
        Log.d(TAG, "addUberAddress(CharSequence text): text: " + text);
        uberAddresses.add(new AddressPoint(System.currentTimeMillis(), text));
    }

    public void addOtherAddress(CharSequence text)
    {
        Log.d(TAG, "addOtherAddress(CharSequence text): text: " + text);
        otherAddresses.add(new AddressPoint(System.currentTimeMillis(), text));
    }

    String getLyftAddresses()
    {
        return getAddresses(lyftAddresses);
    }

    String getUberAddresses()
    {
        return getAddresses(uberAddresses);
    }

    String getOtherAddresses()
    {
        return getAddresses(otherAddresses);
    }

    private String getAddresses(List<AddressPoint> addresses)
    {
        Log.d(TAG, "getAddresses(List<AddressPoint>)");
        String strAddresses = "" + addresses.size() + "\n--------\n";
        for (int i = 0; i < addresses.size(); ++i)
        {
            AddressPoint addresspoint = addresses.get(i);
            strAddresses += addresspoint + "\n--------\n";

            if ((i % 2) != 0)
            {
                strAddresses += "\n\n";
            }
        }
        return strAddresses;
    }

    private void startRunningInForeground()
    {
        //if more than or equal to 26
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        {
            //if more than 26
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
            {
                String CHANNEL_ONE_ID = "Package.Service";
                String CHANNEL_ONE_NAME = "Screen service";
                NotificationChannel notificationChannel = null;
                notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
                        CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_MIN);
                notificationChannel.enableLights(true);
                notificationChannel.setLightColor(Color.RED);
                notificationChannel.setShowBadge(true);
                notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                if (manager != null)
                {
                    manager.createNotificationChannel(notificationChannel);
                }

                Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground);
                Notification notification = new Notification.Builder(getApplicationContext())
                        .setChannelId(CHANNEL_ONE_ID)
                        .setContentTitle("Recording data")
                        .setContentText("App is running background operations")
                        .setSmallIcon(R.drawable.ic_launcher_background)
                        .setLargeIcon(icon)
                        .build();

                Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
                notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);

                startForeground(101, notification);
            }
            //if version 26
            else
            {
                startForeground(101, updateNotification());
            }
        }
        //if less than version 26
        else
        {
            Notification notification = new NotificationCompat.Builder(this)
                    .setContentTitle("App")
                    .setContentText("App is running background operations")
                    .setSmallIcon(R.drawable.ic_launcher_foreground)
                    .setOngoing(true).build();

            startForeground(101, notification);
        }
    }

    private Notification updateNotification()
    {

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, MainActivity.class), 0);

        return new NotificationCompat.Builder(this)
                .setContentTitle("Activity log")
                .setTicker("Ticker")
                .setContentText("app is running background operations")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentIntent(pendingIntent)
                .setOngoing(true).build();
    }

    public void load(final SharedPreferences lSharedPrefs)
    {
        Log.w(TAG, "load(SharedPreferences): started");
        load(lSharedPrefs, lyftAddresses, LYFT_PREFIX);
        load(lSharedPrefs, uberAddresses, UBER_PREFIX);
        load(lSharedPrefs, otherAddresses, OTHER_PREFIX);
        Log.w(TAG, "load(SharedPreferences): completed");
    }

    private void load(final SharedPreferences lSharedPrefs, final List<AddressPoint> lAddrPoints, final String lPrefix)
    {
        Log.w(TAG, "load(SharedPreferences, lAddrPoints, lPrefix)");
        lAddrPoints.clear();
        final int count = lSharedPrefs.getInt(lPrefix + "Count", lAddrPoints.size());
        for (int i = 0; i < count; ++i)
        {
            String address = lSharedPrefs.getString(lPrefix + "Address" + i, null);
            final long time = lSharedPrefs.getLong(lPrefix + "Time" + i, 0);

            //if address or time is invalid skip to the next entry
            if (address == null || time == 0)
            {
                continue;
            }

            final AddressPoint addressPoint = new AddressPoint(time, address);
            lAddrPoints.add(addressPoint);
        }
    }

    private void save(final SharedPreferences lSharedPrefs, final List<AddressPoint> lAddrPoints, final String lPrefix)
    {
        Log.w(TAG, "save(SharedPreferences, lAddrPoints, lPrefix)");
        SharedPreferences.Editor editor = lSharedPrefs.edit();
        final int count = lAddrPoints.size();

        //Save the count
        editor.putInt(lPrefix + "Count", count);
        for (int i = 0; i < count; ++i)
        {
            //Save the entry
            AddressPoint lAddrPoint = lAddrPoints.get(i);
            editor.putLong(lPrefix + "Time" + i, lAddrPoint.getTime());
            editor.putString(lPrefix + "Address" + i, (String) lAddrPoint.getAddress());
        }
        Log.w(TAG, "save(sharedFrefs, List, String): commit");
        editor.commit();
    }

    public void save(final SharedPreferences sharedPreferences)
    {
        Log.w(TAG, "save(SharedPreferences): started");
        Log.w(TAG, "save: lyftAddresses");
        save(sharedPreferences, lyftAddresses, LYFT_PREFIX);

        Log.w(TAG, "save: uberAddresses");
        save(sharedPreferences, uberAddresses, UBER_PREFIX);

        Log.w(TAG, "save: otherAddresses");
        save(sharedPreferences, otherAddresses, OTHER_PREFIX);
        Log.w(TAG, "save(SharedPreferences) completed");
    }

    public void clear()
    {
        lyftAddresses.clear();
        uberAddresses.clear();
        otherAddresses.clear();
    }

    public void delLyftEntry()
    {
        if (lyftAddresses.size() > 0)
        {
            lyftAddresses.remove(lyftAddresses.size() - 1);
        }
    }

    public void delUberEntry()
    {
        if (uberAddresses.size() > 0)
        {
            uberAddresses.remove(uberAddresses.size() - 1);
        }
    }

    public void delOtherEntry()
    {
        if (otherAddresses.size() > 0)
        {
            otherAddresses.remove(otherAddresses.size() - 1);
        }
    }
}

3) activity_main.xml

4) AndroidManifest.xml

Upvotes: 0

Views: 116

Answers (1)

greeble31
greeble31

Reputation: 5042

This (likely) isn't a threading issue, in the normal sense of the word. What's happening is you've got a GeoCoder.getFromLocation() call executing on the main thread. Per the documentation:

The returned values may be obtained by means of a network lookup. ...It may be useful to call this method from a thread separate from your primary UI thread.

That means the method could block for several seconds each time it is called. That's more likely if you're driving through an area of spotty cell coverage. Since the method is called with each location update (roughly every 2 seconds), it's understandable that the UI is hanging.

SUGGESTED FIX

Replace your getAddress() function with an AsyncTask, which moves the getFromLocation() call to a background thread (now your app will truly be multithreaded). Something like this should work:

private class GetFromLocationTask extends AsyncTask<Location, Void, List<Address>> {
    protected List<Address> doInBackground(Location... locs) {
        return gcd.getFromLocation(locs[ 0 ].getLatitude(), locs[ 0 ].getLongitude(), 1);
    }

    protected void onProgressUpdate(Void... progress) {}

    protected void onPostExecute(List<Address> result) {
        //execute the remainder of your getAddress() logic here 
    }
}

Then, execute it using new GetFromLocationTask().execute(location). Call this instead of getAddress(). You don't need to pass a Context to getAddress(), since Service.this will work just as well (it is a Context).

Bonus hint: Note that onLocationChanged() runs on the UI thread, and so does refreshAllViews(). That means your call to runOnUiThread() is superfluous, and it will just execute the given Runnable synchronously.

Upvotes: 1

Related Questions