3

I've searched a lot but I'm always more confused.

I have to create an app that works on background and checks the battery level. How can I create the Service? The Service should start on the device boot and must communicate with the Activity when the battery reaches a certain percentage, so there isn't any interaction from the consumer.

What type of Service have I to create? What are the things I have to do?

Nick21
  • 107
  • 2
  • 10
  • 2
    You need to add the "runs at boot" permission, then set up a broadcast receiver that listens for a specific intent that occurs when the phone boots. That broadcast receiver will start a service intent. Then within that service, use android's BatteryManager to check the battery level. – RogueBaneling Jul 02 '14 at 13:54

2 Answers2

7

Rather than using a long-running service, use an AlarmManager to trigger alarms periodically, which can be received by a BroadcastReceiver and can call a Service (which should stop itself at some point) or an IntentService to perform your battery check. This will cause the least grief for the user, since it will use less resources to keep your application doing what it needs to do, and less grief for you, since the system will be less likely to kill your endlessly-running app.

For example, for an Alarm Receiver:

public class AlarmReceiver extends BroadcastReceiver {
    private static final String TAG = "AlarmReceiver";
    private static final int REQUEST_CODE = 777;
    public static final long ALARM_INTERVAL = DateUtils.MINUTE_IN_MILLIS;


    // Call this from your service
    public static void startAlarms(final Context context) {
        final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        // start alarm right away
        manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, ALARM_INTERVAL,
                getAlarmIntent(context));
    }

    /*
     * Creates the PendingIntent used for alarms of this receiver.
     */
    private static PendingIntent getAlarmIntent(final Context context) {
        return PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(context, AlarmReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT);
    }

    @Override
    public void onReceive(final Context context, final Intent intent) {
        if (context == null) {
            // Somehow you've lost your context; this really shouldn't happen
            return;
        }
        if (intent == null){
            // No intent was passed to your receiver; this also really shouldn't happen
            return;
        }
        if (intent.getAction() == null) {
            // If you called your Receiver explicitly, this is what you should expect to happen
            Intent monitorIntent = new Intent(context, YourService.class);
            monitorIntent.putExtra(YourService.BATTERY_UPDATE, true);
            context.startService(monitorIntent);
        }   
     }
}

For your Boot Receiver:

public class BootReceiver extends BroadcastReceiver{
    private static final String TAG = "BootReceiver";
    private static final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equalsIgnoreCase(ACTION_BOOT)) {
            // This intent action can only be set by the Android system after a boot
            Intent monitorIntent = new Intent(context, YourService.class);
            monitorIntent.putExtra(YourService.HANDLE_REBOOT, true);
            context.startService(monitorIntent);
        }
    }
}

And for your Service:

public class YourService extends Service {
    private static final String TAG = "YourService";
    public static final String BATTERY_UPDATE = "battery";

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null && intent.hasExtra(BootReceiver.ACTION_BOOT)){
            AlarmReceiver.startAlarms(YourService.this.getApplicationContext());
        }
        if (intent != null && intent.hasExtra(BATTERY_UPDATE)){
            new BatteryCheckAsync().execute();
        }

        return START_STICKY;
    }

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


    private class BatteryCheckAsync extends AsyncTask<Void, Void, Void>{

        @Override
        protected Void doInBackground(Void... arg0) {
            //Battery State check - create log entries of current battery state
            IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            Intent batteryStatus = YourService.this.registerReceiver(null, ifilter);

            int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
            boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                             status == BatteryManager.BATTERY_STATUS_FULL;
            Log.i("BatteryInfo", "Battery is charging: " + isCharging);

            int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
            Log.i("BatteryInfo", "Battery charge level: " + (level / (float)scale));
            return null;
        }

        protected void onPostExecute(){
            YourService.this.stopSelf();
        }
    }
}

In your manifest, you'll need to add in:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <!-- Services -->
    <service
        android:name="com.your.package.YourService"
        android:enabled="true"
        android:exported="false"
        android:label="@string/app_name" >
    </service>

    <!-- Receivers -->
    <receiver
        android:name="com.your.package.AlarmReceiver"
        android:enabled="true" />
    <receiver
        android:name="com.your.package.BootReceiver"
        android:enabled="true"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
WalkingFood
  • 136
  • 5
  • There is an issue with the BatteryCheckAsync. needs to extends AsyncTask, doInBackground needs to return true for protected void onPostExecute(Boolean result) to be called. – GulBrillo Jan 04 '17 at 04:39
  • @WalkingFood. Is this still the good practice in 2018? Or is there a better way? – iadcialim24 Dec 23 '18 at 01:59
1

You don't need a Service. Include a Broadcast Receiver in your Application that listens for the Intent.ACTION_BATTERY_CHANGED broadcast. Define that broadcast receiver in your Application Manifest and it will be started automatically whenever there is a battery state change.

The broadcasts include information about the current state of the battery so there is no need for an elaborate (and very expensive!) solution to poll for this information.

This page will provide all the details.

An interesting quote from that page:

You can't easily continually monitor the battery state, but you don't need to.

Generally speaking, the impact of constantly monitoring the battery level has a greater impact on the battery than your app's normal behavior, so it's good practice to only monitor significant changes in battery level—specifically when the device enters or exits a low battery state.

The manifest snippet below is extracted from the intent filter element within a broadcast receiver. The receiver is triggered whenever the device battery becomes low or exits the low condition by listening for ACTION_BATTERY_LOW and ACTION_BATTERY_OKAY.

<receiver android:name=".BatteryLevelReceiver">
<intent-filter>
  <action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
  <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
  </intent-filter>
</receiver>

Other actions you may wish to monitor include: ACTION_BATTERY_CHANGED, ACTION_POWER_CONNECTED, ACTION_POWER_DISCONNECTED

Dale Wilson
  • 9,166
  • 3
  • 34
  • 52
  • But I want to open the application only the first time, it must run in the background all day long – Nick21 Jul 02 '14 at 14:06
  • Android != Windows. You need to learn a new model of how an application runs. If you are "running in the background all day long" your application is "open". It may not have any visible presence to the user, because whether or not you start an Activity is your choice -- and the place you should make that choice is in a broadcast receiver. READ THE PAGE I LINKED TO! – Dale Wilson Jul 02 '14 at 14:57
  • @DaleWilson it is not possible to register Intent.ACTION_BATTERY_CHANGED in AndroidManifest.xml as suggested by you, because ACTION_BATTERY_CHANGED is a sticky intent and hence cannot be declared in AndroidManifest.xml. Source (https://developer.android.com/reference/android/content/Intent.html#ACTION_BATTERY_CHANGED). Do you know any other approach which could work? – user3760100 Jan 29 '17 at 06:38
  • 1
    Hmm.. Looks as if they won't let you "permanently" register a listener for ACTION_BATTERY_CHANGED. I'm guessing that this is because it is a bad idea -- it will have an adverse affect on your battery life!. However if you still want to try it, register a listener for ACTION_BOOT_COMPLETED and have that listener register the listener for ACTION_BATTERY_CHANGED. [check to see if this degrades your battery life!] – Dale Wilson Jan 30 '17 at 15:50