Programming     Travel Logs     Life Is Good     Surfing Online     About Me
Specific knowledge is often highly technical or creative. It cannot be outsourced or automated.
-Naval Ravikant
2018-07-22 22:38:44

Copy this link when reproducing:
http://www.casperlee.com/en/y/blog/228

To test the Java socket server I've created days before, I decided to write an Android application. The main function of this application is to upload the GPS information onto my Java socket server periodically. Clearly in this application, it involves work done in the background without the user interacting with it. So, the best choice is to create a service. But as you may have known, services can be killed by the system if their resources are needed elsewhere. I don't want my service be killed by Android OS (Operating System) automatically, and luckily Android has a type of service named "foreground service", which will never be killed by the Android OS. So that is it, I'm going to create a foreground service.

Just like before, let's take it easy and enjoy a few beautiful photos first. The artist in the following pictures is Gal Gadot - Wonder Woman.

1. Create an Android project named "Personal Cell Service":

    I. The package name is "com.casperlee.personalcellservice".
    II. The minimal SDK is "API 22: Android 5.1 (Lollipop)"
Note: Check the link for the detail steps to create an Android project if necessary.

2. Create the service class.

    I. Create a package named "com.casperlee.personalcellservice.services".

    Note: Check the link for the detail steps to create a new package in an Android project if necessary.

    II. Right click on the package that has been just created, select "New -> Service -> Service(IntentService)", a dialog will be popped up:

    III. Type in the class name and click "Finish" to create a new service class.

    IV. Open the newly-created class, and put the following code into it:

package com.casperlee.personalcellservice.services;

import android.app.IntentService;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.Context;
import android.support.v4.app.NotificationCompat;

import com.casperlee.personalcellservice.R;

public class GPSService extends IntentService {

    // Constants
    private static final int NOTIFICATION_ID = 1978;
    private static final String ACTION_START_UPLOADING = "com.casperlee.personalcellservice.services.action.START_UPLOADING";
    private static final String PARAM_SERVER = "com.casperlee.personalcellservice.services.extra.PARAM_SERVER";
    private static final String PARAM_PORT = "com.casperlee.personalcellservice.services.extra.PARAM_PORT";
    private static final String PARAM_INTERVAL = "com.casperlee.personalcellservice.services.extra.PARAM_INTERVAL";

    // Fields
    private static boolean demandToStop = false;

    // Constructor
    public GPSService() {

        super("GPSService");
    }

    // Public functions
    public static void startActionsStartUploading(
            Context context,
            String aServerName,
            int aPort,
            int anInterval) {

        Intent intent = new Intent(context, GPSService.class);
        intent.setAction(ACTION_START_UPLOADING);
        intent.putExtra(PARAM_SERVER, aServerName);
        intent.putExtra(PARAM_PORT, aPort);
        intent.putExtra(PARAM_INTERVAL, anInterval);
        context.startService(intent);
    }
    public static void startActionStopUploading(Context context) {

        GPSService.demandToStop = true;
    }

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

        NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("GPS Service")
                .setContentText("Synchronize GPS Information to the Cloud")
                .setWhen(System.currentTimeMillis());

        startForeground(NOTIFICATION_ID, builder.build());
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
        super.onDestroy();
    }

    @Override
    protected void onHandleIntent(Intent intent) {

        if (intent != null) {

            final String action = intent.getAction();

            if (ACTION_START_UPLOADING.equals(action)) {

                final String serverName = intent.getStringExtra(PARAM_SERVER);
                final int portNum = intent.getIntExtra(PARAM_PORT, 8080);
                final int interval = intent.getIntExtra(PARAM_INTERVAL, 3000);
                handleActionStartUploading(serverName, portNum, interval);
            }
        }
    }

    // Private functions
    private void handleActionStartUploading(
            String aServerName,
            int aPort,
            int anInterval) {

        while (true) {

            if (demandToStop) {

                break;
            }

            try {

                Thread.sleep(anInterval);

            } catch (InterruptedException ex) {

                ex.printStackTrace();
            }
        }

        demandToStop = false;
    }
}

    Note:
    I. The reason why I chose IntentService instead of Service, is that I don't want to create an extra thread by myself. The IntentService is a sub-class of the Service class, and it is using an extra thread to do the jobs. Please check the link for the official information of this class.
    II. The key to create a foreground service is in the code of the onStartCommand event and the onDestroy event. The "startForeground" function changed the current service to a foreground service, so the Android OS will never kill it automatically now. Since a notification is shown, the end user will know the service is currently running, so he or she gets a chance to decide whether to keep it or terminate it.

4.  Implements the main activity.

    I. Open the layout file "app -> res -> layout -> activity_main.xml", and put the following text in:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="@color/colorBackground"
    tools:context="com.casperlee.personalcellservice.MainActivity">

    <LinearLayout
        android:id="@+id/Layout0"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="@dimen/activity_vertical_margin"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true">

        <TextView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:layout_gravity="center"
            android:gravity="right"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/colorCaption"
            android:text="@string/server_name" />

        <EditText
            android:id="@+id/editServerName"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:theme="@android:style/Theme.Light"
             />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/Layout1"
        android:layout_below="@+id/Layout0"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/activity_vertical_margin"
        android:orientation="horizontal">

        <TextView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:layout_gravity="center"
            android:gravity="right"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/colorCaption"
            android:text="@string/server_port" />

        <EditText
            android:id="@+id/editServerPort"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:theme="@android:style/Theme.Light" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/Layout2"
        android:layout_below="@+id/Layout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/activity_vertical_margin"
        android:orientation="horizontal">

        <TextView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:layout_gravity="center"
            android:gravity="right"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/colorCaption"
            android:text="@string/update_interval" />

        <EditText
            android:id="@+id/editInterval"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:theme="@android:style/Theme.Light" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/Layout3"
        android:layout_below="@+id/Layout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="@dimen/activity_vertical_margin"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:theme="@android:style/Theme.Light"
            android:text="@string/btn_start_caption" />

        <Button
            android:id="@+id/btnStop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:theme="@android:style/Theme.Light"
            android:text="@string/btn_stop_caption" />

        <Button
            android:id="@+id/btnViewLog"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:theme="@android:style/Theme.Light"
            android:text="@string/btn_viewlog_caption" />
    </LinearLayout>
</RelativeLayout>

    II. Open the XML file "app -> res -> values -> colors.xml", and put the following text in:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorBackground">#000000</color>
    <color name="colorCaption">#FFFFFF</color>
</resources>

    III. Open the XML file "app -> res -> values -> strings.xml", and put the following text in:

<resources>
    <string name="app_name">Personal Cell Service</string>
    <string name="server_name">Server: </string>
    <string name="server_port">Port: </string>
    <string name="update_interval">Interval: </string>
    <string name="btn_start_caption">Start</string>
    <string name="btn_stop_caption">Stop</string>
    <string name="btn_viewlog_caption">View Log</string>
</resources>

    IV. Open the file "app -> java-> com.casperlee.personalcellservice -> MainActivity.java", and put the following code in:

package com.casperlee.personalcellservice;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.casperlee.personalcellservice.services.GPSService;

public class MainActivity extends AppCompatActivity {

    // Fields
    private Layout ui;

    // Entrances
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.ui = new Layout();
        this.setListeners();
        this.initializeWidgets();
    }

    // Private functions
    private void setListeners() {

        this.setViewClickListeners();
    }
    private void initializeWidgets() {

        this.ui.editServerName.setText("www.casperlee.com");
        this.ui.editServerPort.setText("9527");
        this.ui.editInterval.setText("3000");
    }
    private void setViewClickListeners() {

        OnMyViewClickListener l = new OnMyViewClickListener();
        this.ui.btnStart.setOnClickListener(l);
        this.ui.btnStop.setOnClickListener(l);
        this.ui.btnViewLog.setOnClickListener(l);
    }

    // Event Handlers
    private void performStartButtonClick() {

        String serverName = this.ui.editServerName.getText().toString();
        int port = Integer.parseInt(this.ui.editServerPort.getText().toString());
        int interval = Integer.parseInt(this.ui.editInterval.getText().toString());
        GPSService.startActionsStartUploading(getApplicationContext(), serverName, port, interval);
    }
    private void performStopButtonClick() {

        GPSService.startActionStopUploading(getApplicationContext());
    }
    private void performViewLogButtonClick() {

        //// TODO: 2017/12/8
    }

    // Classes
    private class Layout {

        private EditText editServerName;
        private EditText editServerPort;
        private EditText editInterval;
        private Button btnStart;
        private Button btnStop;
        private Button btnViewLog;

        public Layout() {

            this.initialize();
        }

        private void initialize() {

            this.editServerName = (EditText)findViewById(R.id.editServerName);
            this.editServerPort = (EditText)findViewById(R.id.editServerPort);
            this.editInterval = (EditText)findViewById(R.id.editInterval);
            this.btnStart = (Button)findViewById(R.id.btnStart);
            this.btnStop = (Button)findViewById(R.id.btnStop);
            this.btnViewLog = (Button)findViewById(R.id.btnViewLog);
        }
    }
    private class OnMyViewClickListener implements View.OnClickListener {

        @Override
        public void onClick(View v) {

            if (v == MainActivity.this.ui.btnStart) {

                MainActivity.this.performStartButtonClick();

            } else if (v == MainActivity.this.ui.btnStop) {

                MainActivity.this.performStopButtonClick();

            } else if (v == MainActivity.this.ui.btnViewLog) {

                MainActivity.this.performViewLogButtonClick();
            }
        }
    }
}

5. Done!