Is it possible to run service in android platform

2020-02-10 07:55发布

问题:

We have been working on developing service for android platform.

In our service we need to send GPS data (Lat and Long) of device to some external REST service after every one minute.

It is running fine for almost 15 minutes after locking of device. But after that it does not send any data.

After unlocking the device, it start again to send data over REST service.

My Code So far

public class MainActivity extends AppCompatActivity {

private PendingIntent pendingIntent;
private PowerManager.WakeLock wakeLock;

public static final String USER_NAME = "USERNAME";

String username;
String password;

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

    Intent alarm = new Intent(this, AlarmReceiver.class);
    boolean alarmRunning = (PendingIntent.getBroadcast(this, 0, alarm, PendingIntent.FLAG_NO_CREATE) != null);
    if(alarmRunning == false) {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarm, 0);
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), 30000, pendingIntent);
    }

    PowerManager mgr = (PowerManager)this.getSystemService(Context.POWER_SERVICE);
    wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakeLock");
    wakeLock.acquire();
 }

public class BackgroundService extends Service  {

private boolean isRunning;
private Context context;
private Thread backgroundThread;

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

@Override
public void onCreate() {
    this.context = this;
    this.isRunning = false;
    this.backgroundThread = new Thread(myTask);
}

private Runnable myTask = new Runnable() {
    public void run() {
        // Do something here
        login("admin","admin");
        stopSelf();
    }
};

@Override
public void onDestroy() {
    this.isRunning = false;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if(!this.isRunning) {
        this.isRunning = true;
        this.backgroundThread.start();
    }
    return START_STICKY;
}

private void login(final String strLatitude, final String strLongitude) {


    class LoginAsync extends AsyncTask<String, Void, String> {

        String charset = "UTF-8";
        HttpURLConnection conn;
        DataOutputStream wr;
        StringBuilder result = new StringBuilder();
        URL urlObj;
        JSONObject jObj = null;
        StringBuilder sbParams;
        String paramsString;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // loadingDialog = ProgressDialog.show(MainActivity.this, "Please wait", "Loading...");
        }

        @Override
        protected String doInBackground(String... params) {
            String uname = params[0];
            String pass = params[1];

            sbParams = new StringBuilder();

            try {
                sbParams.append("name").append("=")
                        .append(URLEncoder.encode(uname, charset));
                sbParams.append("&");
                sbParams.append("password").append("=")
                        .append(URLEncoder.encode(pass, charset));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            try {

                String url="http://192.168.0.122:1234/YegoService.svc/AddVehicleMovement";
                URL object=new URL(url);

                HttpURLConnection con = (HttpURLConnection) object.openConnection();
                con.setDoOutput(true);
                con.setDoInput(true);
                con.setRequestProperty("Content-Type", "application/json");
                con.setRequestProperty("Accept", "application/json");
                con.setRequestMethod("POST");

                JSONObject parent = new JSONObject();

                parent.put("strValidatorID","111");
                parent.put("TXT_LAT", "28.25252525");

                parent.put("TXT_LONG", "77.7777777");
                parent.put("DAT_DATE", "");
                con.connect();

                OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream());
                wr.write(parent.toString());
                wr.flush();
                wr.close();

                InputStream input = con.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                String line;

                while ((line = reader.readLine()) != null) {
                    result.append(line);
                }

                con.disconnect();

            } catch (IOException e) {
                e.printStackTrace();
            }
            catch (Exception ex) {
                ex.printStackTrace();
            }

            return result.toString();
        }

        @Override
        protected void onPostExecute(String result){
            String s = result.trim();
        }
    }

    LoginAsync la = new LoginAsync();
    la.execute("admin", "admin");

}

}

public class AlarmReceiver extends BroadcastReceiver {
String strLatitude;
String strLongitude;

@Override
public void onReceive(Context context, Intent intent) {
    Intent background = new Intent(context, BackgroundService.class);
    context.startService(background);
}
}

What to do?

回答1:

One approach could be for you to rely on the AlarmManager : once you subscribe to an AlarmManager the system itself runs your code at the interval you setup, even if your app is not active. Each time it runs you can decide to process some code... So you completely avoid the need to keep a service alive.

What you need is an Alarm class that will handle the AlarmManager intent.


Create your Alarm :

public class Alarm extends BroadcastReceiver 
{

    private static final String TAG = "Alarm";

    @Override
    public void onReceive(Context context, Intent intent) 
    {   
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "");
        wl.acquire();

        /***************
        Here you can do your stuff...
        This will be triggered every second. 
        Send data from here, or better: call an IntentService
        that will take care of it.
        ****************/

        wl.release();
    }

    public void SetAlarm(Context context)
    {
        Intent i = new Intent(context, Alarm.class);

        boolean alarmUp = (PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_NO_CREATE) != null);

        if (alarmUp)
        {
            // The alarm is already running, do not set it twice
        }
        else
        {
            AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
            am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000, pi); // 1000 Millisec means it will trigger it every second... and RTC_WAKEUP means that it will wake up your device if needed.
        }
    }

    // later on, use this method if you want to manually cancel the AlarmManager :

    public void CancelAlarm(Context context)
    {
        Intent intent = new Intent(context, Alarm.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(sender);
    }
}

In your Manifest declare this Alarm BroadcastReceiver

<receiver 
    android:name=".utils.Alarm"
    android:process=":remote" >
</receiver>

And from where you want in your Activity call this AlarmManager !

Alarm alarm = new Alarm();

@Override
protected void onCreate(Bundle savedInstanceState) 
{

    alarm.SetAlarm(this);

}

// or if elsewhere you need to stop the Alarm :
alarm.CancelAlarm(this);

This is the main idea. Now you need to deal with screen on or off. For this 2 solutions : you can register for the device screen state intent and manage the AlarmManager on/off... or you can let the AlarmManager always running but checking if the device is on/off before sending data...

Hope this will help !



回答2:

You are acquiring the wake lock in your Activity. The problem here is that when the device is locked, your Activity gets pushed to the background. After 15 minutes of inactivity, Android is simply killing the process. This releases the wake lock. The device goes to sleep.

Now, the next time your alarm goes off, the device wakes up, your BroadcastReceiver is triggered, onReceive() is called, it starts your Service, but then the device goes back to sleep because there is no wake lock, so the `Service doesn't do anything.


Another approach, if you want to prevent the phone from going to sleep while your app is running, would be to acquire the wake lock in the Service. In this case, you don't want to call stopSelf() every time your Runnable runs. You would want to keep your Service running until you want to stop it, at which time you would call stopService(). This way, the Service would always be active (even though it isn't doing anything) and it would prevent the device from sleeping through the wake lock. This may, however, put an unacceptable drain on the battery (you'll have to test it).

You need to acquire the wake lock in the BroadcastReceiver and make sure that the Service gets started and acquires a wake lock before the device goes back to sleep. Have a look at WakefulBroadcastReceiver, which you can use to implement this behaviour.



回答3:

Yes, you can run any service even if the device is locked. Even, you can resume the service after rebooting the device.

You can implement GCM Network Manager.

Sample code required:-

<service
    android:name=".MyTaskService"
    android:exported="true"
    android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE">
    <intent-filter>
        <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" />
    </intent-filter>
</service>

Java code :-

mGcmNetworkManager = GcmNetworkManager.getInstance(this);
OneoffTask task = new OneoffTask.Builder()
        .setService(MyTaskService.class)
        .setTag(TASK_TAG_WIFI)
        .setExecutionWindow(0L, 3600L)
        .setRequiredNetwork(Task.NETWORK_STATE_UNMETERED)
        .build();

mGcmNetworkManager.schedule(task);

Fore more info you can visit https://developers.google.com/cloud-messaging/network-manager#run_tasks and read the docs.

You just have to include the gcm services in you project to use GCM network manager. Support 4.0 +

Please accept this answer if this is the solution you want. This may help other developers also.



回答4:

Yes you can implement a background service that it will almost never be killed. But you have to declare it to run in the foreground. you can see what Android Developer site says, by referring to this url(http://developer.android.com/guide/components/services.html) also in this article (http://developer.android.com/guide/components/processes-and-threads.html) they say,

There are five levels in the importance hierarchy and the different types of processes in order of importance (the first process is most important and is killed last):

  1. Foreground process:

A process that is required for what the user is currently doing. A process is considered to be in the foreground if any of the following conditions are true:

  • It hosts an Activity that the user is interacting with (the Activity's onResume() method has been called).
  • It hosts a Service that's bound to the activity that the user is interacting with.
  • It hosts a Service that's running "in the foreground"—the service has called startForeground().
  • It hosts a Service that's executing one of its lifecycle callbacks (onCreate(), onStart(), or onDestroy()).
  • It hosts a BroadcastReceiver that's executing its onReceive() method.

Generally, only a few foreground processes exist at any given time. They are killed only as a last resort—if memory is so low that they cannot all continue to run. Generally, at that point, the device has reached a memory paging state, so killing some foreground processes is required to keep the user interface responsive.

So you have to start your service in the foreground. In order to do this you have implement the service as below.

public class MyForegroundService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        //your code goes here
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        keepServiceAlive();
        //your code goes here
        return(START_NOT_STICKY);
    }

    private void keepServiceAlive() {
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this).setContentTitle(getString(R.string.app_name))
                .setContentText("Hello")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();
        startForeground(Notification.FLAG_ONGOING_EVENT, notification);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.w(getClass().getName(), "Got to stop()!");
        stopForeground(true);
    }
}

Thanks and gud luck..



回答5:

You have to fire the alarm over and over again when the service completes running.

Also you can implement a BroadCastReceiver that starts the service on the device boot.

Check this tutorial: http://ncona.com/2014/04/schedule-your-android-app-to-do-something-periodically/



回答6:

I had the same problem in my app but i solved my issue first create service, use Periodic service. you are able to specify time limit for updating the data. In my case this was the code.

UpdateService.java

public class UpdateServices extends Service implements LocationListener {

    String id, latee, longee;
    // j
    private ProgressDialog pDialog;
    ProgressDialog progressDialog;
    JSONParser jsonParser = new JSONParser();
    DBManager db;
    private static String url_create_locationupdate = "http://192.168.0.175/simple_demo3/classes/create_locationupdate.php";
    private static final String TAG_SUCCESS = "success";
    public static String LOG = "Log";
    private final Context mContext;
    boolean isGPSEnabled = false;
    boolean isNetworkEnabled = false;
    boolean canGetLocation = false;
    Location location; // location
    double latitude; // latitude
    double longitude; // longitude
    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 3; // 0 meters
    private long MIN_TIME_BW_UPDATES; // 10 second
    private long MIN_LENGTH_BW_UPDATES;
    SharedPreferences mPref;
    protected LocationManager locationManager;

    public UpdateServices(Context context) {
        this.mContext = context;
    }

    public UpdateServices() {
        super();
        mContext = UpdateServices.this;
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Log.i(LOG, "Service started");

        mPref = getSharedPreferences("mFile", 0);
        MIN_TIME_BW_UPDATES = mPref.getLong("mint", 1) * 1000 * 60;
        MIN_LENGTH_BW_UPDATES = mPref.getLong("kmeter", 1) * 1000;
        Log.i("asd", "This is sparta");
        latitude = getLocation().getLatitude();
        longitude = getLocation().getLongitude();

        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(LOG, "Service created");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(LOG, "Service destroyed");

    }

    public Location getLocation() {
        try {
            locationManager = (LocationManager) mContext
                    .getSystemService(LOCATION_SERVICE);

            isGPSEnabled = locationManager
                    .isProviderEnabled(LocationManager.GPS_PROVIDER);
            isNetworkEnabled = locationManager
                    .isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            if (!isGPSEnabled && !isNetworkEnabled) {
            } else {
                this.canGetLocation = true;
                if (isNetworkEnabled) {
                    locationManager.requestLocationUpdates(
                            LocationManager.NETWORK_PROVIDER, 5000,
                            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
                    Log.d("Network", "Network");
                    if (locationManager != null) {
                        location = locationManager
                                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                        if (location != null) {
                            latitude = location.getLatitude();
                            longitude = location.getLongitude();
                        }
                    }
                }
                // if GPS Enabled get lat/long using GPS Services
                if (isGPSEnabled) {
                    if (location == null) {
                        locationManager.requestLocationUpdates(
                                LocationManager.GPS_PROVIDER,
                                MIN_TIME_BW_UPDATES,
                                MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
                        Log.d("GPS Enabled", "GPS Enabled");
                        if (locationManager != null) {
                            location = locationManager
                                    .getLastKnownLocation(LocationManager.GPS_PROVIDER);
                            if (location != null) {
                                latitude = location.getLatitude();
                                longitude = location.getLongitude();
                            }
                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return location;
    }

    @Override
    public void onLocationChanged(Location location) {
        // this will be called every second
        String laty = Double.toString(getLocation().getLatitude());
        String lagy = Double.toString(getLocation().getLongitude());
        db = new DBManager(mContext);
        db.open();
        db.mInsertGPSCor(laty, lagy);
        Toast.makeText(
                getApplicationContext(),
                "Your Location is - \nLat: " + location.getLatitude()
                        + "\nLong: " + location.getLongitude(),
                Toast.LENGTH_LONG).show();

        Toast.makeText(UpdateServices.this, "record entered",
                Toast.LENGTH_SHORT).show();
        db.close();
// store in server
        new CreateNewProduct(this).execute();

    }

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

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }

    class CreateNewProduct extends AsyncTask<String, String, String> {
        private Context mContext;

        public CreateNewProduct(Context context) {
            super();
            mContext = context;
        }

        @Override
        protected void onPreExecute() {
            try {
                super.onPreExecute();
                progressDialog = ProgressDialog.show(mContext,
                        "Press Back to Cancel", "Sending Data to Server..",
                        true, false);
            } catch (Exception e) {
                // TODO: handle exception
            }

        }

        /**
         * Creating product
         * */
        protected String doInBackground(String... args) {
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            params.add(new BasicNameValuePair("ID", id));
            params.add(new BasicNameValuePair("LATITUDE", latee));
            params.add(new BasicNameValuePair("LONGITUDE", longee));

            JSONObject json = jsonParser.makeHttpRequest(
                    url_create_locationupdate, "POST", params);

            try {
                int success = json.getInt(TAG_SUCCESS);

                if (success == 1) {

                    return "done";
                } else {
                    // failed to create product
                    return "fail";
                }
            } catch (JSONException e) {
                e.printStackTrace();
                return "exec";
            }

        }

        /**
         * After completing background task Dismiss the progress dialog
         * **/
        protected void onPostExecute(String file_url) {
            if (progressDialog.isShowing())
                progressDialog.dismiss();

            if (file_url.equalsIgnoreCase("done")) {

                show.message(mContext, "uploading successed");
            }
            if (file_url.equalsIgnoreCase("fail")
                    || file_url.equalsIgnoreCase("exec")) {
                try {
                    show.message(mContext, "uploading failed");
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    public void onConnectionSuspended(int arg0) {
        // TODO Auto-generated method stub

    }

}

and Main.java

public class Main extends Activity {

    Button btn_startGps, btn_stopGps;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.auto_gps_update);
        btn_startGps = (Button) findViewById(R.id.button_service);
        btn_stopGps = (Button) findViewById(R.id.button_stopservice);


        btn_startGps.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {           
                startService(new Intent(About.this, UpdateServices.class));
                Toast.makeText(About.this, "Service Started",
                        Toast.LENGTH_SHORT).show();
            }
        });

        btn_stopGps.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                stopService(new Intent(About.this, UpdateServices.class));
            Log.e("sss", "ddddd");
                Toast.makeText(About.this, "Service Stopped",
                        Toast.LENGTH_SHORT).show();

            }
        });
}

but here an issue service is not stop here to stop service

Because i have return

 return START_STICKY;

in onStartCommand(...)

read more at START_STICKY and START_NOT_STICKY

and Official docs



回答7:

If you run your app on API 21+, using JobScheduler which is described in Google documentation is also a best approach.

Also, if you don't want to change your code structure, you can use your service to keep the CPU ON even if screen is off. Read how to keep CPU On from Google Documentation. Just add permission in your manifest <uses-permission android:name="android.permission.WAKE_LOCK" /> and in your Service.onCreate, put :

PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
        "MyWakelockTag");
wakeLock.acquire();

And release in Service.onDestroy with wakelock.release(). But be aware that it drains your battery. But if you said that the device will be always plugged in a source power, I think it will not be a problem. Just in case, it will be better to have an admin UI in the app to stop the service manually.



回答8:

In Manifest file,
<service android:name=".MyService"></service>

MyService.java

public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
       // your code here
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Intent it = new Intent(MyService.this, MyService.class);
        getApplication().startService(it); // If service will destroy, Start the service again
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }


}

to run the service, add this to your Activity,

    Intent it = new Intent(getApplication(), MyService.class);
getApplicationContext().startService(it);