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.
-
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.
-
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
-
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
-
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
- 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. - 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. - 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. - 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(); }
- 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>