I have an Android app that needs to sync to the internet, but as soon as the phone goes to sleep I can't access the internet. It only happens when the user uses the "battery mode", when it turns off the data after 15 minutes. I wrote a test app and its turning the data on, but it still does connect to the server.
What I tried:
When I turn the data manually off, then the app is turning it on and it works
I also tried WakeLock, but it did not help.
The alarm works as expected, even when the phone goes to sleep for hours
Tested on Motorola Atrix Android 2.3.3. I can't rely on Wifi. In real life it will sync every week. How can we make it possible?
AlarmManager:
alarm_manager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarm_manager.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(), 15000, pending);
AlarmReceiver:
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("MYTAG", "RECEIVED getMobileDataEnabled: " + getMobileDataEnabled(context));
if (!isOnline(context)) {
Log.d("MYTAG", "NO INET");
if (turnOnInet(context)) {
Log.d("MYTAG", "INET IS ON");
}
}
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://xxx.xxx.xxx.xxx/ping/pong/moto/");
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
nameValuePairs.add(new BasicNameValuePair("short_code", "ROFL"));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
httpclient.execute(httppost);
Log.d("MYTAG", "POST FINISHED");
}
catch (Exception e) {
Log.e("MYTAG", "MYTAG", e);
}
}
public boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null){
Log.d("MYTAG", "isAvailable: "+netInfo.isAvailable());
}
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
}
return false;
}
public boolean turnOnInet(Context context) {
ConnectivityManager mgr = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (mgr == null) {
Log.d("MYTAG", "ConnectivityManager == NULL");
return false;
}
try {
Method setMobileDataEnabledMethod = mgr.getClass().getDeclaredMethod("setMobileDataEnabled", boolean.class);
if (null == setMobileDataEnabledMethod) {
Log.d("MYTAG", "setMobileDataEnabledMethod == null");
return false;
}
setMobileDataEnabledMethod.invoke(mgr, true);
}
catch(Exception e) {
Log.e("MYTAG", "MYTAG", e);
return false;
}
return true;
}
private boolean getMobileDataEnabled(Context context) {
ConnectivityManager mgr = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (mgr == null) {
Log.d("MYTAG", "getMobileDataEnabled ConnectivityManager == null");
return false;
}
try {
Method method = mgr.getClass().getMethod("getMobileDataEnabled");
return (Boolean) method.invoke(mgr);
} catch (Exception e) {
Log.e("MYTAG", "MYTAG", e);
return false;
}
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
First, you need to get that HttpPost code out of the BroadcastReceiver and into an IntentService. Never do network I/O on the main application thread, and onReceive() is called on the main application thread. For example, if you take too long, Android will terminate your code partway through your Internet operation.
Second, given the IntentService, you need to use a WakeLock. That may steer you to use my WakefulIntentService, which handles both problems. Or, use WakefulBroadcastReceiver, which has the same purpose.
Third, delete turnOnInet() and getMobileDataEnabled(). You do not need them, they are unreliable, and in particular turnOnInet() is user-hostile -- if the user wanted mobile data on, they would have turned it on.
Now, given all of that, in your onHandleIntent() of your IntentService() (or your doWakefulWork() of your WakefulIntentService), if you do not have an Internet connection right away, as a temporary workaround, SystemClock.sleep() for a second and try again, repeating a few times in a loop. If you find that you are getting Internet access after a bit, then you can consider getting more sophisticated (e.g., listening for connectivity change broadcasts rather than polling, though this would drive you away from WakefulIntentService and into a regular Service with your own background thread and a state machine for WakeLock management). Or, just stick with the sleep() -- it's unlikely to be the end of the world if you tie up this background thread for a few seconds. If you do not get connectivity after a modest amount of time, though, please do not keep trying indefinitely, as there are any number of reasons why you might not get a connection, including user-driven bandwidth management on Android 4.0+.
I'd suggest to change a bit that approach, which is not bad at all, it's even nice to make sure that you are always synced, with the only problem that you are not giving the user the chance to decide, since if I, as an user, decide to turn off my data I just don't want anybody to turn it on. There could be several reasons for that, and any of them should be enough, but say that you get out of the country and you have no international data plan and by accident or default you have Data roaming activated.
If I'd discover that some certain app turned my data on and spent a sensitive amount of money I'd be pretty pissed, and I'll be personal.
A more proper approach and straight solution as well would be to make a hard/full sync from time to time whenever the user opens your app or has access to some wifi connection (ConnectivityManager is your friend) based on some easy conditions (last time sync longer than a week, outdated saved data, inconsistencies, etc) and make a soft sync (update data in the background) in the rest of cases.
Moreover syncing periodically means wasting user data in case that user doesn't use the app.
Ultimately, this turns your app into perfect candidate to be shut down by the system every once in a while.
Hope it helps. Keep us updated with your progress.
Related read: Optimizing downloads for efficient network access
Not an exact answer - but this approach would absolutely ruin the battery life. The whole point of sleep is to save battery power and this app would do nothing more than be an annoyance for customers no matter how useful the features are.
What I would suggest is that if it is absolutely necessary to connect to the internet while the app is not in use - set a trigger for when the phone wakes up. If it is not completely necessary it probably would be best to simply reconnect to the internet every time the app is opened.
Related
I would like to know recommended and best practices of checking internet connection in mobile applications.
In most of the apps I developed with my team mates, we check the internet connection before any action that requires internet connection. But I see many applications that notifies when device is disconnected from internet.
I would like to get a clear understanding about this topic and I believe so many developers like me hesitate about how to make this check properly when they are developing an application.
Any help or ideas will be appreciated.
You can use broadcast reciever to handle wifi changes.
Reciever code:
private BroadcastReceiver WifiStateChangedReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
int extraWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN);
switch (extraWifiState) {
case WifiManager.WIFI_STATE_DISABLED: {
handler.sendEmptyMessage(DATA_DISCONNECTED);
}
break;
case WifiManager.WIFI_STATE_DISABLING: {
}
break;
case WifiManager.WIFI_STATE_ENABLED: {
handler.sendEmptyMessage(DATA_CONNECTED);
}
break;
case WifiManager.WIFI_STATE_ENABLING: {
}
break;
case WifiManager.WIFI_STATE_UNKNOWN: {
}
break;
}
}
};
Registering reciever:
this.registerReceiver(this.WifiStateChangedReceiver,
new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
To check mobile data connection changes You can use TelephonyManager:
PhoneStateListener pslCell = new PhoneStateListener() {
#Override
public void onDataConnectionStateChanged(int state) {
switch (state) {
case TelephonyManager.DATA_DISCONNECTED: {
handler.sendEmptyMessage(DATA_DISCONNECTED);
}
break;
case TelephonyManager.DATA_SUSPENDED: {
handler.sendEmptyMessage(DATA_CONNECTED);
}
break;
}
}
};
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(pslCell, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
I recommend You to store network status in static variables. Also You need to check network status every application start.
It is always related to the project We are doing.
If you are only doing the application which requires only internet connection at the time of any action then checking internet connection before perfoming the action is better it will remove the overhead of checking internet Connection every time
I prefer this method
But if we really requires full net connection every time if we need to load the latest news or chat applications we need to notify at every time.
For this we have to use the Appropriate Connection Api of every Mobile Platform
Android Provides Connection BroadCast Recivers For this
It totally depends upon your Applications requirements that what kind of your Application is.
Some times:
1) We have to check the internet connection before any action that requires it.
2) We periodically check the internet connection after some specific defined interval.
3) We need immediate response of internet connection's status, so our App continuously checking it out and immediately send us notification status of internet failure or success, so we are able to tackle our App in such scenario.
Hope it helps.
I have an alarm application. I generally know the lifecycle of the receiver and how to use WakeLock.
Today however I was contacted by an user that have sent me a really strange log and complained that his alarm hasn't started until he have had unlocked the phone by himself. I used to have problems with phones going back to sleep after receiver completed its work and before activity was started, but creating WakeLock in the receiver seemed to fix the problem. At least until today - from log it seems that onReceive method wasn't called at all until user has unlocked phone by himself.
Facts:
it is the first case I have heard of
it has happened a few times to the user, but not every time
log is prepared by adding text to SQLite database. It doesn't seem to delay application in any significant way
infomation from onReceive was recorded over 100 seconds after expected alarm start time. It is the first method call in onReceive
alarm was started just after user has unlocked the phone
I use AlarmManager.RTC_WAKEUP flag
user says he doesn't have any custom rom. I wait for answer if he has any custom/special lockscreen
phone model is Sony Xperia U ST25A, Android 4.0.4
Any ideas what could be causing this problem? Is it possible that BroadcastReceiver's "inside" WakeLock doesn't work somehow?
EDIT:
I would like to emphasize the issue here - BroadcastReceiver should keep phone awake during its whole onReceive method. However in my case, it is either that
phone falls to sleep before onReceive methods end (even before finishing "logging call")
phone is not awaken by receiver at all
Also, I would like to point out the fact that user has stated clearly - alarm has started precisely when he has unlocked phone by himself. Couple of times.
Some code:
#Override
public void onReceive(Context context, Intent intent) {
Logger.initialize(context, "AlarmReceiver");
...
}
Logger methods:
public synchronized static void initialize(Context context, String text) {
try {
if (mInstance == null) { // this is the block that is runned
BugSenseHandler.initAndStartSession(context, BUGSENSE_ID);
mInstance = new Logger(context);
log("== Logger initialized == from "
+ (text != null ? text : "")); // it stores times as well. Said
// that alarm was started over 100
// seconds after it should
} else {
log("logger initialized again from "
+ (text != null ? text : ""));
}
} catch (Exception e) {
try {
BugSenseHandler.sendException(e);
mInstance = null;
} catch (Exception e2) {
}
}
}
Take a look at WakefulIntentService from Commonsware
Well, I want to check the version of a site (this part I know how) every 6h or so.
So, I was thinking about making a service for this and use AlarmManager for it.
Since I need Internet to check the version of the site, I need something to see if the internet is on or to see when it's turned on. After the time passed I'll
So my questions (yep, not just one!) are:
Does AlarmManager works even if the display goes to sleep? When the device wakes up it knows how many time as passed and if passed more that 6h it executes the task?
How to check when internet is available?
How to know when internet is turned on? (some kind of broadcast?)
Is this a good solution?
Alarm Manager:
The alarm manager does not have anything to do with the display state, so Yes it can work even if the screen is off.
Network Avaiability snippet:
public boolean isNetworkAvailable() {
Context context = getApplicationContext();
ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) {
boitealerte(this.getString(R.string.alert),"getSystemService rend null");
} else {
NetworkInfo[] info = connectivity.getAllNetworkInfo();
if (info != null) {
for (int i = 0; i < info.length; i++) {
if (info[i].getState() == NetworkInfo.State.CONNECTED) {
return true;
}
}
}
}
return false;
}
This function will return true if the network is available, false if it is not (airplane mode, out of reach, etc.)
Don't forget to add permission in your manifest
A possible solution
Have broadcast receiver for the screen off & screen on events like below,
public class ScreenReceiver extends BroadcastReceiver {
public static boolean wasScreenOn = true;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
// do whatever you need to do here
wasScreenOn = false;
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
// and do whatever you need to do here
wasScreenOn = true;
}
}
}
In this receiver give the logic for requesting if network is available..
About the alarm manager. Here is a possible code:
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis() + 10000, 6*60*60*1000, pendingIntent);
The first parameter influence how device behave:
RTC - alarm does not wake the device up; if it goes off while the device is asleep, it will not be delivered until the next time the device wakes up
RTC_WAKEUP - wake up the device when it goes off
I have an Android application that has this statement that posts data to the server.
HttpResponse httpresponse = httpclient.execute(httppost);
When I start the application, this statement executes fine. I have made my application automatially start after booting. When it boots up, I am getting an IOEXCeption at this line with message my hostname. The host is running all the time as the same statement works fine when I start the application manually. This issue occurs only when the phone boots up.
Has anyone encountered this issue before?
CommansWare,
I have this new method in my onReceive call to check the network connection. With this my problem is resolved, meaning it posts the "in boot Completed" message and goes on executing the http request successfully but it looks like it does not exit the while look as the "not online" message showing up endlessly. How can it happen? Is this not the correct solution?
public void onReceive(Context context, Intent intent)
{
while(!isOnline(context) )
{
Toast toast = Toast.makeText(context, "Not online", Toast.LENGTH_SHORT);
toast.show();
}
Toast toast = Toast.makeText(context, "in boot completed", Toast.LENGTH_SHORT);
toast.show();
}
}
protected boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnected())
{
Toast toast = Toast.makeText(context, "returned true", Toast.LENGTH_SHORT);
toast.show();
return true;
}
else
{
Toast toast = Toast.makeText(context, "returned false", Toast.LENGTH_SHORT);
toast.show();
return false;
}
}
There may not be an active Internet connection at that point in time. After all, the device just powered on from being turned off. This is no different than any other OS.
You may need to do something to delay your HTTP operations until such time as an Internet connection is available. For example, your BOOT_COMPLETED receiver could use AlarmManager and set() to arrange to get control again in a few minutes, by which time the device will hopefully be more stable. Of course, you will also want to use ConnectivityManager to determine whether there is an Internet connection at that time as well, and you may prefer to do some sophisticated receiver work to listen for a ConnectivityManager broadcast, to perhaps do your HTTP work a bit more quickly than you would get with the AlarmManager route.
My app is setting an alarm:
AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
mgr.setRepeating(
AlarmManager.RTC_WAKEUP,
firstRun,
interval,
makePendingIntent(context));
Works fine. If I go into system settings -> applications, and force-stop my app, it seems that this also has the effect of canceling any alarms I had scheduled. Is this true? If so, I'm in a weird situation where my last-known settings are that the user had in fact set the alarm, but the system may have canceled behind my back, so I'm now showing the user the wrong status as to whether the alarm is set or not.
Thanks
Yes, it's true. All you can do, as far as I know, is to make your status right. To check if your alarms are still there you have to take 2 steps:
Atempt to create your PendingIntent with FLAG_NO_CREATE - the function checkPendingIntent will be exactly like makePendingIntent except for this flag in PendingIntent.getBroadcast and a check of the result - if the alarm has been canceled in an ordinary way (by yourself, of course if you called cancel() for your PendingIntents) or if your app crashed without Force Stop (i.e. with an uncaught exception), PendingIntent.getBroadcast will return null.
If the PendingIntent exists your alarm might be set. To check it you have to dump system information about all alarms and search for yours there. To dump the info you have to call
String collectAlarmsInfo() {
StringBuilder result = new StringBuilder();
try {
Process process = Runtime.getRuntime().exec("dumpsys alarm");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
result.append("\n");
}
} catch (IOException e) {
Log.e(TAG, "Could not retrieve data", e);
}
return result.toString();
}
To use dumpsys you need to have user-permission DUMP. When you have the dump you can identify you alarms in it by you package name, so the check will be
boolean alarmIsSet = collectAlarmsInfo().contains(<your package name>);