可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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):
- 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);