Android learning notes - Services

Backstage silent workers

01 service

  service is a solution to realize the background running of programs in android It is 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 other user interface. Even if the program is switched to the background or the user opens another application, the service can still run normally

02 there will be an error when the child thread updates the UI

  Android UI is also thread unsafe If you want to update UI elements, you must do it in the main thread, otherwise there will be exceptions

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("hello");
            }
        }).start();
    }
});
//The thread was started to modify the UI of the main thread, resulting in an error

03 asynchronous message processing case

private Handler handler = new Handler(Looper.myLooper()){
    @Override
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case UPDATE_TEXT:
                //Perform UI operations
                textView.setText("hello");
                break;
            default:
                break;
        }
    }
};
new Thread(new Runnable() {
    @Override
    public void run() {
        //Sub thread update UI
        Message message= new Message();
        message.what = UPDATE_TEXT;
        handler.sendMessage(message);
    }
}).start();

04 asynchronous message processing mechanism

1. Message

  Message is a Message passed between threads Can carry a small amount of information The what field, arg1 and arg2 fields of Message can carry integer data The obj field carries an obj object

2. Handler

  processor, used to send and process messages The sendMessage() method of Handler is generally used for sending A series of processed messages will arrive in the Handler's handleMessage() method

3. MessageQueue

  message queue It is used to store all messages sent through the Handler Messages will always exist in the message queue waiting to be processed There will only be one MessageQueue object per thread

4. Looper

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

technological process:

  1. Create a Handler object in the main thread and override the handleMessage() method
  2. If the child thread wants to perform UI operation, it creates a Message object and sends the Message through the Handler object of the main thread
  3. The message is added to the MessageQueue queue in the Handler
  4. Looper always fetches messages from MessageQueue and returns them to Handler's handleMessage() method

05 using AsyncTask

Abstract class, which needs to be inherited when used There are three generic parameters that can be specified

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

1. Params

  parameters that need to be passed in when executing AsyncTask can be used in background tasks

2. Progress

   during background task execution, if the current progress needs to be displayed on the interface, the generic specified here is used as the progress unit

3. Result

   after the task is executed, if the result needs to be returned, use the generic specified here as the return value type

Here you can specify that the first generic parameter is void It means that no parameters need to be passed to the background task during execution The second generic parameter is specified as Integer, indicating that Integer data is used as the progress display unit The third parameter is Boolean, which means that the execution result is fed back with Boolean data

The methods that often need to be rewritten are as follows:

1. onPreExecute()
This method will be called before the background task is executed, for initialization operations on some interfaces, such as display.
A progress bar dialog box, etc.

02. 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 task execution result through the return statement. If the third generic parameter of AsyncTask specifies Void, you can not return the task execution result. 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 onpublishProgress (Progress...) method to complete it.
03. onProgressUpdate(Progress...)
When the publishProgress(Progress...) method is invoked in the background task, the onProgressUpdate (Progress...) method will be called 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.
04. onPostExecute(Result)
  this method will be called soon after the background task is executed and returned through the return statement. Number of returned
Data is passed to this method as a parameter. You can use the returned data to perform some UI operations, such as reminding task execution
Line, and close the progress bar dialog box.

The following codes are incomplete

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

    @Override
    protected void onPreExecute() {
        //1 display progress dialog box
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
       try{
           while (true){
               //2 time consuming operation
               int downloadPercent = doDownload();//This is a fictional download method;
               publishProgress(downloadPercent); //Because UI operation cannot be performed, download progress is transferred to onProgressUpdate() method
               if (downloadPercent >=100){
                   break;
               }
           }
       }catch (Exception e){
           return false;
       }
       return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        //4. Perform UI operation to display the download progress
        progressDialog.setMessage("DownLoaded" + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        //5 display download results
        progressDialog.dismiss();// close dialog boxes
        if (aBoolean){
            Toast.makeText(context,"Download succeed",Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(context,"Download failed",Toast.LENGTH_SHORT).show();
        }
    }
}

To perform this task, simply

//The code runs in child threads
new DownloadTask().execute();

    here is a fictional method called download () 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. Because the doInBackground() method runs in the sub thread, UI operation cannot be performed here, so we can call the publishProgress() method and pass in the current download progress, so the onProgressUpdate() method will be called soon, and UI operation can be performed here. 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 run 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.

    in short, 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.

06 service usage

1 define a service

  new->service->service . Check the exported and enabled attributes

Override onCreate(), onStartCommand(), and onDestroy()

  among them, onCreate() method will be called when the Service is created, onStartCommand() method will be called every time the Service is started, and onDestroy() method will be called when the Service is destroyed.

2 start and restart services

Code case

Main activity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    Button startService = (Button) findViewById(R.id.startService);
    startService.setOnClickListener(this);
    Button stopService = (Button) findViewById(R.id.stopService);
    stopService.setOnClickListener(this);
}

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.startService:
            //01 open service
            Intent startIntent = new Intent(this,MyService.class);
            startService(startIntent);
            break;
        case R.id.stopService:
            //02 shut down service
            Intent stopIntent = new Intent(this,MyService.class);
            stopService(stopIntent);
            break;
        default:
            break;
    }
}

Custom service

public class MyService extends Service {
    private static final String TAG = "Service";
    public MyService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate: executed");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: executed");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: executed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

Result: the oncreate method and onstartcommand method execute

3 communicate with activities and services

Principle: callback

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private MyService.DownloadBinder downloadBinder;

    //01 to bind services
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //Calling methods in a service
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

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

        Button bindService = (Button) findViewById(R.id.bindService);
        bindService.setOnClickListener(this);
        Button unbindService = (Button) findViewById(R.id.unbindService);
        unbindService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bindService:
                //02 generate service and bind
                Intent bindIntent = new Intent(this,MyService.class);
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                break;
            case R.id.unbindService:
                //03 unbinding service
                Intent unbindIntent = new Intent(this,MyService.class);
                unbindService(connection);
                break;
            default:
                break;
        }
    }
}

When binding a service, only the oncreate method of the service will be called, not the onstartcommand method

07 service life cycle

  onCreate(), onStartCommand(), onBind() and onDestroy() we used earlier
Methods are methods that may be called back within the Service life cycle.

  once the startservice () method of Context is called anywhere in the project, the corresponding Service will start and call back the onstartcommand () method. 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 or recycled by the system. Note that although onStartCommand() is executed 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.

Front desk service

Add the following code in onCreate of service

//Create notification management
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

//Create channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
    NotificationChannel channel = new NotificationChannel(
        "vashon","Test notification",NotificationManager.IMPORTANCE_HIGH);
    manager.createNotificationChannel(channel);
}
//Details of my message
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
    this,0,intent,0);
//Create notification
Notification notification = new NotificationCompat.Builder(this,"vashon")
    .setContentTitle("My notice")
    .setContentText("This is the content of the notice")
    .setSmallIcon(R.drawable.ic_launcher_background)//Cannot be RGB
    .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher_background))//Large icon
    .setColor(Color.parseColor("#ff0000 ") / / small icon color
    .setAutoCancel(true) //Click to cancel
    .setContentIntent(pendingIntent)   //Notification details
    .build();
startForeground(1,notification);

At manifest Add permissions to XML

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

You can see the front desk service in the notice bar Can't remove

08 using intentservice (processing tasks in child threads)

  because the service runs in the main thread by default, sometimes the service needs to process time-consuming logic, which is prone to ANR Intentservice solves this problem

Create IntentService class

public class MyIntentService extends IntentService {

    private static final String TAG = "MyIntentService";


    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //Print current process id
        Log.d(TAG, "onHandleIntent: Thread id is" + Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: executed");
    }
}

Add code to main activity

case R.id.startIntentService:
//Print current process id
    Log.d(TAG, "Thread id is " + Thread.currentThread().getId());
    Intent intentService = new Intent(this,MyIntentService.class);
    startService(intentService);
    break;

The results are as follows:

D/MainActivity: Thread id is 2
D/MyIntentService: onHandleIntent: Thread id is2508
D/MyIntentService: onDestroy: executed

Keywords: Android

Added by keithschm on Mon, 03 Jan 2022 11:58:35 +0200