Android learning services

Posted by blastbum on Fri, 11 Feb 2022 23:39:41 +0100

1. What is the service

  • Service is a solution to realize the background operation of programs in Android. It is very suitable for performing tasks that do not need to interact with users and require long-term operation
  • The operation of the service does not depend on any user interface. Even if the program is switched to the background or the user opens another application, the service can still run normally
  • It depends on the process in which the application is created, not the process in which the service is running. When an application process is killed, all services that depend on the process will also stop running

2. Multithreaded programming

1. Basic usage of thread

Method 1: inherit thread class

class MyThread extends Thread {
    @Override
    public void run() {
        // Handling specific logic
    }
}

new MyThread().start();

Method 2: implement runnable interface

class MyThread implements Runnable {
    @Override
    public void run() {
        // Handling specific logic
    }
}

MyThread myThread = new MyThread();
new Thread(myThread).start();

Method 3: anonymous class

new Thread(new Runnable() {
    @Override
    public void run() {
        // Handling specific logic
    }
}).start();

2. Update UI in child thread

First define the component layout

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/change_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Change Text" />
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello world"
        android:textSize="20sp" />
</RelativeLayout>

Two controls are defined in the layout file. TextView is used to display a Hello world string in the middle of the screen, and Button is used to change the content displayed in TextView. We hope to change the string displayed in TextView to Nice to meet you after clicking Button.

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView text;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (TextView) findViewById(R.id.text);
        Button changeText = (Button) findViewById(R.id.change_text);
        changeText.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        text.setText("Nice to meet you");
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

The above program will crash because the UI operation is executed in the subroutine. To avoid the crash, you need a set of asynchronous message processing methods.

Modify mainactivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    public static final int UPDATE_TEXT = 1;
    private TextView text;
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    // UI operations can be performed here
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };
    ...
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message); // Send Message object
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

You can see that an integer constant update is defined first_ Text, used to indicate the action of updating TextView. Then add a Handler object and override the handleMessage() method of the parent class to process the specific Message here. If the value of the what field of Message is found to be equal to UPDATE_TEXT, change the content displayed in TextView to Nice to meet you.

Let's take another look at the code in the click event of the Change Text button: instead of directly performing UI operations in the sub thread, we create a message (android.os.Message) object and specify the value of its what field as UPDATE_TEXT, then call the sendMessage() method of Handler to send the Message.

3. Parsing asynchronous message processing mechanism

Asynchronous Message processing in Android is mainly composed of four parts: Message, Handler, MessageQueue and Looper.

  1. Message

    Message is a message passed between threads. It can carry a small amount of information internally, which is used to exchange data between different threads. In the previous section, we used the what field of message. In addition, we can also use arg1 and arg2 fields to carry some integer data and obj field to carry an Object object.

  2. Handle

    It is mainly used for sending and processing messages. Generally, the sending message uses the Handler's sendMessage() method, and the sent message will eventually be delivered to the Handler's handleMessage() method after a series of twists and turns

  3. MessageQueue

    MessageQueue means message queue. It is mainly used to store all messages sent through the Handler. This part of the message will always exist in the message queue and wait to be processed. There will only be one MessageQueue object per thread

  4. Looper

    Looper is the steward of MessageQueue in each thread. After calling Looper's loop() method, it will enter an infinite loop. Then whenever a message is found in MessageQueue, it will be taken out and passed to Handler's handleMessage() method. There will also be only one looper object per thread

Asynchronous message processing flow:

  • First, you need to create a handle object in the main thread and override the handlemessage method
  • When the child thread needs UI operation, it creates a Message object and sends the Message through the Handle
  • After that, the message will be added to the queue of MessageQueue for processing
  • Looper will always try to get the message to be processed from the MessageQueue, and finally distribute it back to the handle's hanlmessage method

4. Use AsyncTask

AsyncTask is an abstract class, so if we want to use it, we must create a subclass to inherit it. During inheritance, we can specify three generic parameters for the AsyncTask class

  • Params . The parameters that need to be passed in when executing AsyncTask can be used in background tasks
  • Progress . When background tasks are executed, if the current progress needs to be displayed on the interface, the generic specified here is used as the progress unit.
  • Result . After the task is executed, if the result needs to be returned, the generic type specified here is used as the return value type.

Therefore, a simple custom asynctask can be written as

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    ...
}

//The first generic parameter is specified as Void, which means that it is not necessary to pass in parameters to the background task when executing AsyncTask.
//The second generic parameter is specified as Integer, which means that Integer data is used as the progress display unit.
//If the third generic parameter is specified as boolean, it means that Boolean data is used to feed back the execution result.

Of course, our customized DownloadTask is still an empty task and cannot perform any actual operation. We still need to rewrite several methods in AsyncTask to complete the customization of the task

  1. onPreExecute()
    This method is called before the background task is executed, for initialization operations on some interfaces, such as displaying a progress bar dialog box and so on.
  2. doInBackground(Params...)
    All the code in this method will run in the sub thread, and we should deal with all the time-consuming tasks here. Once the task is completed, you can return the execution result of the task through the return statement. If the third generic parameter of AsyncTask specifies Void, you can not return the execution result of the task. Note that UI operations are not allowed in this method. If you need to update UI elements, such as feedback on the execution progress of the current task, you can call the publishProgress (Progress...) method to complete it.
  3. onProgressUpdate(Progress...)
    When the publishProgress(Progress...) method is invoked in the background task, the onProgressUpdate (Progress...) method is invoked quickly, and the parameters carried in the method are passed in the background task. In this method, the UI can be operated, and the interface elements can be updated accordingly by using the values in the parameters.
  4. onPostExecute(Result)
    When the background task is completed and returned through the return statement, this method will be called soon. The returned data will be passed to this method as parameters. You can use the returned data to carry out some UI operations, such as reminding the result of task execution and closing the progress bar dialog box.

Therefore, a relatively complete custom AsyncTask can be written as follows:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        progressDialog.show(); // Show progress dialog box
    }
    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload(); // This is a fictional method
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        // Update download progress here
        progressDialog.setMessage("Downloaded " + values[0] + "%");
    }
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss(); // Close the progress dialog box
        // Here you will be prompted to download the results
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show();
        }
    }
}

1. Execute the specific download task in the doInBackground() method. The code in this method runs in the child thread, so it will not affect the operation of the main thread. Note that there is a fictional method called download (), which is used to calculate the current download progress and return it. We assume that this method already exists. After getting the current download progress, we should consider how to display it on the interface. The doInBackground() method runs in the sub thread, and UI operations cannot be performed here. Therefore, we can call the publishProgress() method and pass in the current download progress, so the onProgressUpdate() method will be called soon, Here you can perform UI operations. When the download is completed, the doInBackground() method will return a boolean variable, so the onPostExecute() method will be called soon, and this method is also running in the main thread. Then, we will pop up the corresponding Toast prompt according to the download results, so as to complete the whole DownloadTask task.

2. To put it simply, the trick of using AsyncTask is to execute specific time-consuming tasks in the doInBackground() method, perform UI operations in the onProgressUpdate() method, and perform the finishing work of some tasks in the onPostExecute() method.

3. If you want to start this task, just write the following code:

new DownloadTask().execute();

3. Basic usage of services

1. Define a service

  • Create a new ServiceTest project, and then right-click com example. servicetest→New→Service→Service

  • Rewrite other methods in the Service

    public class MyService extends Service {
        ...
        @Override
        public void onCreate() {
            super.onCreate();
        }
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            return super.onStartCommand(intent, flags, startId);
        }
        @Override
        public void onDestroy() {
            super.onDestroy();
        }
    }
    //The onCreate() method will be called when the service is created,
    //The onStartCommand() method is called every time the service starts
    //The onDestroy() method will be called when the service is destroyed.
    
  • Registration: it has been registered automatically

2. Start and stop services

First modify the activity_ main. The code in XML adds two buttons in the layout file, which are used to start and stop the service

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/start_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Service" />
    <Button
        android:id="@+id/stop_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop Service" />
</LinearLayout>
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startService = (Button) findViewById(R.id.start_service);
        Button stopService = (Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
                startService(startIntent); // Start service
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent); // Out of Service
                break;
            default:
                break;
        }
    }
}

How can I verify that the service has been successfully started or stopped? The simplest way is to add print logs to several methods of MyService

public class MyService extends Service {
    ...
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyService", "onDestroy executed");
    }
  • The onCreate() method is called when the service is first created

  • The onStartCommand() method is called every time the service is started

3. Communication of activities and services

With the help of the onBind() method we just ignored, the relationship between activities and services is closer. I hope to provide a download function in MyService. Then I can decide when to start downloading and check the download progress at any time. The idea of realizing this function is to create a special Binder object to manage the download function and modify the code in MyService

public class MyService extends Service {
    private DownloadBinder mBinder = new DownloadBinder();
    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.d("MyService", "startDownload executed");
        }
        public int getProgress() {
            Log.d("MyService", "getProgress executed");
            return 0;
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    ...
}

A new DownloadBinder class is created and inherited from Binder, and then the method of starting download and viewing download progress is provided inside it. Of course, these are only two simulation methods, which do not realize the real function. We print a line of log in these two methods respectively An instance of DownloadBinder is created in MyService, and then returned in the onBind() method

Let's take a look at how to call these methods in the service in the activity. First, you need to add two buttons in the layout file to modify the activity_ main. The two buttons are used to bind and unbind services respectively

<Button
        android:id="@+id/bind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service" />
    <Button
        android:id="@+id/unbind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Unbind Service" />
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        Button bindService = (Button) findViewById(R.id.bind_service);
        Button unbindService = (Button) findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            ...
            case R.id.bind_service:
                Intent bindIntent = new Intent(this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE); // Binding service
                break;
            case R.id.unbind_service:
                unbindService(connection); // Unbinding service
                break;
            default:
                break;
        }
    }
}

Code parsing

  • First, an anonymous class of ServiceConnection is created, in which the onServiceConnected() method and onServiceConnected() method are rewritten. These two methods will be called when the activity and service are successfully bound and the connection between the activity and service is disconnected
  • In the onServiceConnected() method, we get the DownloadBinder instance through downward transformation. With this instance, the relationship between activities and services becomes very close
  • Of course, activities and services have not been bound yet. This function is completed in the click event of the Bind Service button. As you can see, here we are still building a Intent object, then calling the bindService() method to bind MainActivity and MyService. The bindService() method receives three parameters. The first parameter is the newly constructed intent object, the second parameter is the instance of ServiceConnection created earlier, and the third parameter is a flag bit, which is passed in here_ AUTO_ Create means that the service is automatically created after the activity and service are bound. This will cause the onCreate() method in myservice to be executed, but the onStartCommand() method will not be executed

4. Service life cycle

  • If the startService() method of Context is called anywhere in the project, the corresponding service will be started and the onStartCommand() method will be called back
  • If the service has not been created before, onCreate() method will be executed before onStartCommand() method
  • After the service is started, it will remain running until the stopService() or stopSelf() method is called
  • Although onStartCommand() executes once every time the startService() method is called, in fact, there is only one instance of each service. So no matter how many times you call the startService() method, just call the stopService() or stopSelf() method once, and the service will stop.
  • In addition, you can also call bindService() of Context to obtain the persistent connection of a service, and the service will be recalled at this time
    onBind() method in.
  • If the service has not been created before, the onCreate() method will execute before the onBind() method
  • After that, the caller can get an instance of the IBinder object returned in the onBind() method, so that it can communicate with the service freely. As long as the connection between the caller and the service is not disconnected, the service will remain running.
  • When the startService() method is called and the stopService() method is called, the onDestroy() method in the service will be executed, indicating that the service has been destroyed
  • A service calls both the startService() method and the bindService() method. In this case, how can the service be destroyed? In this case, the destroy () method and the destroy () method are called at the same time.

5. More skills of service

1. Use front desk service

Modify the code in MyService

@Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
        NotificationManager manager;
        manager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);//Which service did you get
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        if(Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
            //Channels are only needed on Android O
            NotificationChannel notificationChannel = new NotificationChannel("channelid1","channelname",NotificationManager.IMPORTANCE_DEFAULT);
            //If import is used here_ Noene needs to open the channel in the system settings before the notification can pop up normally
            manager.createNotificationChannel(notificationChannel);
        }
        Notification notification = new NotificationCompat.Builder(MyService.this,"channelid1")
                .setContentTitle("This is content title") //title
                .setContentText("THis is trxt")  //Text content
                .setWhen(System.currentTimeMillis()) //Specifies when the was created
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) //Large icon
                .setAutoCancel(true)
                .setContentIntent(pi)
                .build();
//        manager.notify(1, notification);
        startForeground(1, notification);

    }

Be sure to include the license in the configuration file

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

2. Use IntentService

  • The code in the service runs in the main thread by default. If you directly process some time-consuming logic in the service, ANR (application not responding) is easy to occur.

  • At this time, we need to use Android multithreading programming technology. We should start a sub thread in each specific method of the service, and then deal with those time-consuming logic here

  • However, once the service is started, it will always be running. You must call stopService() or stopSelf() to stop the service. Therefore, if you want to realize the function of automatically stopping a service after execution, you can write it like this

    public class MyService extends Service {
        ...
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // Handling specific logic
                    stopSelf();
                }
            }).start();
            return super.onStartCommand(intent, flags, startId);
        }
    }
    
  • Although this writing method is not complicated, there will always be some programmers who forget to start the thread or call the stopSelf() method. In order to simply create an asynchronous service that will stop automatically, Android specifically provides an IntentService class, which solves the two embarrassments mentioned above. Let's take a look at its usage.

  • Create a new MyIntentService class that inherits from IntentService

    public class MyIntentService extends IntentService {
        public MyIntentService() {
            super("MyIntentService"); // Call the parameterized constructor of the parent class
        }
        @Override
        protected void onHandleIntent(Intent intent) {
            // Print the id of the current thread
            Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId());
        }
        @Override
        public void onDestroy() {
            super.onDestroy();
            Log.d("MyIntentService", "onDestroy executed");
        }
    }
    //First, you need to provide a parameterless constructor, and you must call the parametered constructor of the parent class inside it. Then implement the abstract method onHandleIntent() in the subclass. In this method, you can deal with some specific logic, and you don't have to worry about the problem of ANR, because this method is already running in the sub thread. To confirm, we print the id of the current thread in the onHandleIntent() method. In addition, according to the characteristics of IntentService, the service should stop automatically after running, so we rewrite the onDestroy() method and print a line of Log here to confirm whether the service has stopped
    
  • Modify activity_main.xml, add a button to start MyIntentService

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        ...
        <Button
            android:id="@+id/start_intent_service"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Start IntentService" />
    </LinearLayout>
    
  • Then modify the code in MainActivity

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ...
            Button startIntentService = (Button) findViewById(R.id.start_intent_
                service);
            startIntentService.setOnClickListener(this);
        }
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                ...
                case R.id.start_intent_service:
                    // Print the id of the main thread
                    Log.d("MainActivity", "Thread id is " + Thread.currentThread().
                        getId());
                    Intent intentService = new Intent(this, MyIntentService.class);
                    startService(intentService);
                    break;
                default:
                    break;
            }
        }
    }
    
  • Finally, don't forget that services are needed in Android manifest Registered in XML

    <service android:name=".MyIntentService" />
    

6. Service best practices - Download

1. Add dependency

implementation 'com.squareup.okhttp3:okhttp:3.10.0'

2. Define interface: used to monitor and call back various statuses during downloading

public interface DownloadListener {
    void onProgress(int progress);
    void onSuccess();
    void onFailed();
    void onPaused();
    void onCanceled();
}
  1. AsyncTask to realize the download function
public class DownloadTask extends AsyncTask<String, Integer, Integer> {
    public static final int TYPE_SUCCESS = 0;
    public static final int TYPE_FAILED = 1;
    public static final int TYPE_PAUSED = 2;
    public static final int TYPE_CANCELED = 3;
    private DownloadListener listener;
    private boolean isCanceled = false;
    private boolean isPaused = false;
    private int lastProgress;
    public DownloadTask(DownloadListener listener) {
        this.listener = listener;
    }
    @Override
    protected Integer doInBackground(String... params) {
        InputStream is = null;
        RandomAccessFile savedFile = null;
        File file = null;
        try {
            long downloadedLength = 0; // Length of downloaded files
            String downloadUrl = params[0];
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
            String directory = Environment.getExternalStoragePublicDirectory
                (Environment.DIRECTORY_DOWNLOADS).getPath();
            file = new File(directory + fileName);
            if (file.exists()) {
                downloadedLength = file.length();
            }
            long contentLength = getContentLength(downloadUrl);
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadedLength) {
                // The downloaded bytes are equal to the total bytes of the file, indicating that the download has been completed
                return TYPE_SUCCESS;
            }
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    // Breakpoint download, specify which byte to download from
                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                    .url(downloadUrl)
                    .build();
            Response response = client.newCall(request).execute();
            if (response != null) {
                is = response.body().byteStream();
                savedFile = new RandomAccessFile(file, "rw");
                savedFile.seek(downloadedLength); // Skip downloaded bytes
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {
                    if (isCanceled) {
                        return TYPE_CANCELED;
                    } else if(isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        total += len;
                        savedFile.write(b, 0, len);
                        // Calculate the percentage of Downloads
                        int progress = (int) ((total + downloadedLength) * 100 /
                            contentLength);
                        publishProgress(progress);
                    }
                }
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (savedFile != null) {
                    savedFile.close();
                }
                if (isCanceled && file != null) {
                    file.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return TYPE_FAILED;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        int progress = values[0];
        if (progress > lastProgress) {
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }
    @Override
    protected void onPostExecute(Integer status) {
        switch (status) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
            default:
                break;
        }
    }
    public void pauseDownload() {
        isPaused = true;
    }
    public void cancelDownload() {
        isCanceled = true;
    }
    private long getContentLength(String downloadUrl) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        Response response = client.newCall(request).execute();
        if (response != null && response.isSuccessful()) {
            long contentLength = response.body().contentLength();
            response.body().close();
            return contentLength;
        }
        return 0;
    }
}

4. Create a new service and download it in the background

package com.example.servicebestpractice;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

import java.io.File;

public class DownloadService extends Service {

    private DownloadTask downloadTask;
    private String downloadUrl;
    private DownloadListener listener = new DownloadListener() {
        @Override
        public void onProgress(int progress) {
            getNotificationManager().notify(1, getNotification("Downloading...", progress));
        }

        @Override
        public void onSuccess() {
            downloadTask = null;
            // When the download is successful, close the foreground service notification and create a notification of successful download
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Success", -1));
            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onFailed() {
            downloadTask = null;
            // When the download fails, the foreground service notification will be closed and a download failure notification will be created
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Failed", -1));
            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPaused() {
            downloadTask = null;
            Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onCanceled() {
            downloadTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
        }
    };

    private DownloadBinder mBinder = new DownloadBinder();

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

    class DownloadBinder extends Binder {
        public void startDownload(String url) {
            if (downloadTask == null) {
                downloadUrl = url;
                downloadTask = new DownloadTask(listener);
                downloadTask.execute(downloadUrl);
                startForeground(1, getNotification("Downloading...", 0));
                Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
            }
        }

        public void pauseDownload() {
            if (downloadTask != null) {
                downloadTask.pauseDownload();
            }
        }

        public void cancelDownload() {
            if (downloadTask != null) {
                downloadTask.cancelDownload();
            } else {
                if (downloadUrl != null) {
                    // To cancel the download, delete the file and close the notification
                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                    String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                    File file = new File(directory + fileName);
                    if (file.exists()) {
                        file.delete();
                    }
                    getNotificationManager().cancel(1);
                    stopForeground(true);
                    Toast.makeText(DownloadService.this, "Canceled",
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    private NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    private Notification getNotification(String title, int progress) {
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);

        NotificationManager manager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);//Which service did you get
        if(Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
            //Channels are only needed on Android O
            NotificationChannel notificationChannel = new NotificationChannel("channelid1","channelname",NotificationManager.IMPORTANCE_DEFAULT);
            //If import is used here_ Noene needs to open the channel in the system settings before the notification can pop up normally
            manager.createNotificationChannel(notificationChannel);
        }

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"channelid1");
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if (progress >= 0) {
            // When progress is greater than or equal to 0, the download progress needs to be displayed
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }

}

5. Create layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/start_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Download" />
    <Button
        android:id="@+id/pause_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pause Download" />
    <Button
        android:id="@+id/cancel_download"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel Download" />
</LinearLayout>

6. Modify main function

package com.example.servicebestpractice;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private DownloadService.DownloadBinder downloadBinder;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (DownloadService.DownloadBinder) service;
        }
    };

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

        Button startDownload = (Button) findViewById(R.id.start_download);
        Button pauseDownload = (Button) findViewById(R.id.pause_download);
        Button cancelDownload = (Button) findViewById(R.id.cancel_download);
        startDownload.setOnClickListener(this);
        pauseDownload.setOnClickListener(this);
        cancelDownload.setOnClickListener(this);
        Intent intent = new Intent(this, DownloadService.class);
        startService(intent); // Start service
        bindService(intent, connection, BIND_AUTO_CREATE); // Binding service
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission. WRITE_EXTERNAL_STORAGE }, 1);
        }
    }

    @Override
    public void onClick(View v) {
        if (downloadBinder == null) {
            return;
        }
        switch (v.getId()) {
            case R.id.start_download:
                String url = "http://m801.music.126.net/20210521131408/990a3651eb78bce603cc875a7dd1b735/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/8811932284/7595/79d6/d9d1/194395c4365fa1d3ad032e65bed5370a.mp3";
                downloadBinder.startDownload(url);
                break;
            case R.id.pause_download:
                downloadBinder.pauseDownload();
                break;
            case R.id.cancel_download:
                downloadBinder.cancelDownload();
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                           int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "Denial of permission will prevent the program from being used", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(connection);
    }
}

7. Modify the configuration file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicebestpractice">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service
            android:name=".DownloadService"
            android:enabled="true"
            android:exported="true"></service>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Topics: Java Android