当前位置:网站首页>Backstage silent laborer, inquiry service

Backstage silent laborer, inquiry service

2020-12-08 09:53:47 zouchanglin

Service as Android One of the four components , It is an application component that can run for a long time in the background without providing an interface . Services can be started by other application components , And even if users switch to other applications , The service will continue to run in the background . It should be noted that the service does not automatically start threads , All code runs in the main thread by default , So manually creating child services needs to be done internally , And perform specific tasks here , Otherwise, the main thread may be blocked .

<!-- more -->

Android Multithreaded programming

Asynchronous messaging mechanism

About multithreading programming is actually and Java Agreement , Whether it's inheritance Thread Or to achieve Runnable Interfaces can be implemented . stay Android What you need to master is to update in the child thread UI,UI It's controlled by the main thread , So the main thread is also called UI Threads .

Only the original thread that created a view hierarchy can touch its views.

Although updates in child threads are not allowed UI, however Android Provides a set of asynchronous message processing mechanism , Perfect solution in the child thread operation UI The problem of , That's using Handler. Let's review the use of Handler to update UI Usage of :

public class MainActivity extends AppCompatActivity {
    private static final int UPDATE_UI = 1001;
    private TextView textView;

    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            if(msg.what == UPDATE_UI) textView.setText("Hello Thread!");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv_main);
    }

    public void updateUI(View view) {
        // new Thread(()-> textView.setText("Hello Thread!")).start(); Error!
        new Thread(()->{
            Message message = new Message();
            message.what = UPDATE_UI;
            handler.sendMessage(message);
        }).start();
    }
}

Using this mechanism, we can solve the problem of updating in sub threads UI The problem of , Let's analyze Android How asynchronous message processing works :Android Asynchronous message processing in mainly consists of 4 Component composition :Message,Handler,MessageQueue and Looper.
1、Message: Messages passed between threads , It can carry a small amount of information inside , Used to exchange data between different threads .
2、Handler: handler , It is mainly used to send and process messages . Sending messages usually uses Handler Of sendMessage() Method , And the message sent after a series of tossing and turning , It will eventually reach Handler Of handleMessage() In the method .
3、MessageQueue: Message queue , It is mainly used to store all passing through Handler Message sent . This part of the message will always exist in the message queue , Waiting to be dealt with . There will only be one... In each thread MessageQueue object .

4、Looper Is in each thread MessageQueue The steward of , call Looper Of loop() After the method , It's going to go into an infinite cycle , And every time I find out MessageQueue There is a message in , Will take it out , And deliver it to Handler Of handleMessage() In the method . There will only be one in each thread Looper object .

The whole process of asynchronous message processing : First you need to create a... In the main thread Handler object , And rewrite handleMessage() Method . And then when it needs to be done in the child thread UI In operation , Just create one Message object , And pass Handler Send this message . After that, the message will be added to MessageQueue Waiting to be processed in the queue of , and Looper Will always try to MessageQueue Remove pending messages from , Last minute Handler Of handleMessage() In the method . because Handler It was created in the main thread , So at this time handleMessage() Method will also run in the main thread , So we can do it safely here UI Operation . The flow of the whole asynchronous message processing mechanism is shown in the figure below :

AsyncTask

But for the sake of more convenience, we are in the sub thread UI To operate ,Android There are also some other useful tools , such as AsyncTask.AsyncTask The implementation principle behind it is also based on the asynchronous message processing mechanism , It's just Android It's a good package for us . So let's look at this first AsyncTask The basic usage of , because AsyncTask Is an abstract class , So if we want to use it , You have to create a subclass to inherit it . We can inherit for AsyncTask Class specified 3 Generic parameters , this 3 Parameters are used as follows :

Params: In execution AsyncTask The parameter passed in is required when , Can be used in background tasks .
Progress: Background task execution , If you need to display the current progress on the interface , Then use the generics specified here as the progress unit .
Result: When the task is finished , If you need to return the result , Then use the generics specified here as the return value type .

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private final int REQUEST_EXTERNAL_STORAGE = 1;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void startDownload(View view) {
        verifyStoragePermissions(this);
        ProgressBar progressBar = findViewById(R.id.download_pb);
        TextView textView = findViewById(R.id.download_tv);
        new MyDownloadAsyncTask(progressBar, textView).execute("http://xxx.zip");
    }


    class MyDownloadAsyncTask extends AsyncTask<String, Integer, Boolean> {
        private ProgressBar progressBar;
        private TextView textView;

        public MyDownloadAsyncTask(ProgressBar progressBar, TextView textView) {
            this.progressBar = progressBar;
            this.textView = textView;
        }

        @Override
        protected Boolean doInBackground(String... strings) {
            String urlStr = strings[0];
            try {
                URL url = new URL(urlStr);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                InputStream inputStream = conn.getInputStream();
                //  Get the total length of the file 
                int length = conn.getContentLength();
                File downloadsDir = new File("...");
                File descFile = new File(downloadsDir, "xxx.zip");
                int downloadSize = 0;
                int offset;
                byte[] buffer = new byte[1024];
                FileOutputStream fileOutputStream = new FileOutputStream(descFile);
                while ((offset = inputStream.read(buffer)) != -1){
                    downloadSize += offset;
                    fileOutputStream.write(buffer, 0, offset);
                    
                    //  Throw out the progress of task execution 
                    publishProgress((downloadSize * 100 / length));
                }
                fileOutputStream.close();
                inputStream.close();
                Log.i(TAG, "download: descFile = " + descFile.getAbsolutePath());
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }

        //  Perform result processing in the main thread 
        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if(aBoolean){
                textView.setText(" Download complete , The file is located in ..xx.zip");
            }else{
                textView.setText(" Download failed ");
            }
        }

        //  Task schedule update 
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            //  New progress received , Perform processing 
            textView.setText(" Downloaded " + values[0] + "%");
            progressBar.setProgress(values[0]);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            textView.setText(" No Click to download ");
        }
    }
}

1、onPreExecute(): The method will be called before the background task starts executing , It is used to initialize some interfaces , For example, display a progress bar dialog box .

2、doInBackground(): All the code in the method will run in the child thread , We should be here to deal with all the time-consuming tasks . Once the task is completed, it can pass return Statement to return the execution result of the task , If AsyncTask The third generic parameter of specifies Void, You don't need to return the task execution result . Be careful , It can't be done in this method UI Operation of the , Update if necessary UI Elements , For example, feedback on the progress of the current task , You can call publishProgress() Method to accomplish .

3、onProgressUpdate(): When called in the background task publishProgress() After the method ,onProgressUpdate() Method will be called soon , The parameters carried in this method are passed in background tasks . In this method, you can UI To operate , The interface elements can be updated by using the values in the parameters .

4、onPostExecute(): When the background task is completed and passed return When the statement returns , This method will be called soon . The returned data is passed to this method as an argument , You can use the returned data to UI operation , For example, remind the result of task execution , And close the progress bar dialog box .

Basic usage of services

Service first serves as Android One of , Naturally, it should be in Manifest Declaration in the document , This is a Android Common features of the four components . Create a new one MyService Class inherits from Service, Then declare it in the manifest file .

Service creation and startup

MyService.java:

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

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

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.tim.basic_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=".MyService"
            android:enabled="true"
            android:exported="true" />

        <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>

You can see ,MyService There are two properties in the service tag of ,exported Property indicates whether to allow other programs other than the current program to access the service ,enabled Property indicates whether the service is enabled . And then in MainActivity.java Start the service in :

//  Start the service 
startService(new Intent(this, MyService.class));

Stop of service ( The destruction )

How to stop the service ? stay MainActivity.java Stop this service in :

Intent intent = new Intent(this, MyService.class);
//  Start the service 
startService(intent);
//  Out of Service 
stopService(intent);

Actually Service You can also override other methods :

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

    public MyService() {
    }

    //  establish 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    //  start-up 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

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

    //  Unbundling 
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.i(TAG, "unbindService: ");
    }

    //  The destruction 
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}

Actually onCreate() Method is called when the service is first created , and onStartCommand() Method is called every time the service is started , Because we just clicked... For the first time Start Service Button , The service has not been created at this time , So both methods will execute , After that, if you click a few more times in a row Start Service Button , Only onStartCommand() Method can be executed :

Service binding and unbinding

In the example above , Although the service is started in the activity , But after starting the service , Activities have little to do with services . This is similar to the event notification service : You can start it ! Then the service goes to work on its own , But the event didn't know what the service was doing , And how it's done . So it's about service binding .

For example MyService A download function is provided in , Then in the activity, you 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 , modify MyService.java:

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

    private DownloadBinder mBinder = new DownloadBinder();
    
    static class DownloadBinder extends Binder {
        public void startDownload() {
            //  The simulation starts to download 
            Log.i(TAG, "startDownload executed");
        }

        public int getProgress() {
            //  Simulation back to download progress 
            Log.i(TAG, "getProgress executed");
            return 0;
        }
    }

    public MyService() {}

    //  establish 
    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate: ");
    }

    //  start-up 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    //  binding 
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind: ");
        return mBinder;
    }

    //  Unbundling 
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.i(TAG, "unbindService: ");
    }

    //  The destruction 
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy: ");
    }
}

MainActivity.java as follows :

public class MainActivity extends AppCompatActivity {

    private MyService.DownloadBinder downloadBinder;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder 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);
    }

    public void aboutService(View view) {
        int id = view.getId();
        Intent intent = new Intent(this, MyService.class);
        switch (id){
            case R.id.start_btn:
                startService(intent);
                break;
            case R.id.stop_btn:
                stopService(intent);
                break;
            case R.id.bind_btn:
                //  Incoming here BIND_AUTO_CREATE Indicates that the service is automatically created after the activity and service are bound 
                bindService(intent, connection, BIND_AUTO_CREATE);
                break;
            case R.id.unbind_btn:
                unbindService(connection);
                break;
        }
    }
}

This ServiceConnection In the anonymous class of onServiceConnected() Methods and onServiceDisconnected() Method , These two methods will be called when the activity and the service are successfully bound and unbound respectively . stay onServiceConnected() In the method , By going down, you get DownloadBinder Example , With this example , The relationship between activities and services becomes very close . Now we can call... In the activity according to the specific scenario DownloadBinder Any of them public() Method , That is to say, it realizes the function that command service can do whatever it needs ( Although the realization of startDownload And getProgress It's easy to implement ).

It should be noted that , Any service is generic across the entire application , namely MyService Not only can we MainActivity binding , You can also bind to any other activity , And they all get the same after the binding DownloadBinder example .

Service life cycle

Once called startServices() Method , The corresponding service will be started and called back onStartCommand(), If the service is not created , It will call onCreate() establish Service object . The service will remain running after it is started , until stopService() perhaps stopSelf() Method is called . No matter startService() How many times have I been called , But as long as Service The object is ,onCreate() Method will not be executed , So you only need to call stopService() perhaps stopSelf() Method will stop the corresponding service .

Through bindService() To get a persistent connection to a service , At this time, it will call back onBind() Method . Similarly , If this service has not been created before ,oncreate() The method will precede onBind() Method execution . after , The caller can get onBind() Method IBinder Instance of object , So you can communicate freely with the service . As long as the connection between the caller and the service is not broken , The service will keep running .

Then it calls startService() Call again bindService() Methodical , How to destroy the service in this case ? according to Android System mechanism , Once a service is started or bound , It's going to be running all the time , We must make the above two conditions not satisfied at the same time , Service can be destroyed . therefore , In this case, call stopService() and unbindService() Method ,onDestroy() Method will be executed .

More tips for service

The basic usage of the service is described above , Let's take a look at more advanced techniques for services .

Use front desk services

Almost all services run in the background , The system priority of the service is still relatively low , When the system runs out of memory , It is possible to recycle the services running in the background . If you want the service to stay up and running , It will not be recycled due to insufficient system memory , You can use the front desk service . such as QQ The floating window of the phone , Or some weather applications need to display the weather in the status bar .

public class FrontService extends Service {
    String mChannelId = "1001";

    public FrontService() {
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        Intent intent = new Intent(this, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat.Builder(this, mChannelId)
                .setContentTitle("This is content title.")
                .setContentText("This is content text.")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                        R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1, notification);
    }
}

Use IntentService

The code in the service runs in the main thread by default , If you deal with some time-consuming logic directly in the service , It's easy to show up ANR The situation of . So multithreading is needed , When a time-consuming operation is encountered, a child thread can be opened in each specific method of the service , Then I'll deal with the time-consuming logic here . It can be written in the following form :

public class OtherService extends Service {
    public OtherService() {}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(()->{
            // TODO  Perform time-consuming operations 
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
    ...
}

however , Once the service is started , It's going to be running all the time , Must call stopService() perhaps stopSelf() Method to stop the service . therefore , If you want to implement the function of automatically stopping a service after execution , You can write like this :

public class OtherService extends Service {
    public OtherService() {}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(()->{
            // TODO  Perform time-consuming operations 
            stopSelf();
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
    ...
}

Although it's not complicated , But there will always be programmers who forget to turn on threads , Or forget to call stopSelf() Method . In order to simply create an asynchronous 、 Automatically stopped services ,Android There is a special IntentService class , This class solves the two kinds of embarrassments mentioned above , Let's take a look at its usage :

MyIntentService.java

public class MyIntentService extends IntentService {
    private static final String TAG = "MyIntentService";
    private int count = 0;
    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        count++;
        Log.i(TAG, "onHandleIntent: count = " + count);
    }
}

MainActivity.java:

for (int i = 0; i < 10; i++) {
    Intent intent = new Intent(MainActivity.this, MyIntentService.class);
    startService(intent);
}

Reference material :《 First line of code 》

Original address : 《 Backstage silent laborer , Inquiry service 》

版权声明
本文为[zouchanglin]所创,转载请带上原文链接,感谢
https://chowdera.com/2020/12/202012080953224818.html