Article directory
In addition to Thread, Android also plays the role of Thread: AsyncTask, HandlerThread, and IntentService.
- AsyncTask: it encapsulates thread pool and handler internally, so as to update UI in sub thread.
- HandlerThread: a thread that can use a message loop, and inside it can use a Handler.
- IntentService: use HandlerThread internally to execute tasks, and exit automatically after completion. (compared with background thread) because it is a component, it has high priority and is not easy to be killed.
Thread is the smallest unit of operating system scheduling, and it is a kind of limited resource, which can not be produced without limitation. And the creation and destruction of threads need corresponding overhead. When there are a large number of threads, the system will schedule the threads by the way of time slice rotation, so threads cannot be parallel unless the number of threads is less than or equal to the number of CPUs. Therefore, a thread pool is needed, which can cache a certain number of threads and avoid the system overhead caused by frequent thread creation and destruction.
1, Thread form in Android
1.1 AsyncTask
AsyncTask is used to process asynchronous tasks in the online process pool and send processing progress and results to the UI thread.
1.1.1 usage
The basic usage of AsyncTask is as follows:
private void testAsyncTask() { //It is usually instantiated in the main thread. (in fact, on 9.0, the sub thread creates an instance, and then the main thread execute is OK.) //The three generic parameters represent parameter type, progress type and result type in turn. //These overridden methods cannot be called directly AsyncTask<Integer, Integer, String> task = new AsyncTask<Integer, Integer, String>() { @Override protected void onPreExecute() { super.onPreExecute(); //Main thread execution, before asynchronous task Log.i(TAG, "testAsyncTask onPreExecute: "); } @Override protected String doInBackground(Integer... integers) { Log.i(TAG, "testAsyncTask doInBackground: "); //Tasks perform time-consuming operations in the process pool try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //Issue progress publishProgress(50); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //Reissue progress publishProgress(100); return "I am the result. Parameter is" + integers[0]; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); //In the main thread, after the asynchronous task is completed Log.i(TAG, "testAsyncTask onPostExecute: "+s); } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); //Execute in the main thread, and execute after calling publishProgress() Log.i(TAG, "testAsyncTask onProgressUpdate: Speed of progress:"+values[0]+"%"); } @Override protected void onCancelled() { super.onCancelled(); //Cancel tasks } }; //execute must be executed on the main thread and can only be executed once task.execute(100); }
The execution result log is as follows:
2020-01-14 11:29:03.510 13209-13209/com.hfy.demo01 I/hfy: testAsyncTask onPreExecute: 2020-01-14 11:29:03.511 13209-13282/com.hfy.demo01 I/hfy: testAsyncTask doInBackground: 2020-01-14 11:29:04.558 13209-13209/com.hfy.demo01 I/hfy: testAsyncTask onProgressUpdate: Speed of progress:50% 2020-01-14 11:29:05.589 13209-13209/com.hfy.demo01 I/hfy: testAsyncTask onProgressUpdate: Speed of progress:100% 2020-01-14 11:29:05.590 13209-13209/com.hfy.demo01 I/hfy: testAsyncTask onPostExecute: I am the result. Parameter is100
1.1.2 principle analysis:
First look at the construction method
public AsyncTask() { this((Looper) null); } public AsyncTask(@Nullable Handler handler) { this(handler != null ? handler.getLooper() : null); } /** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ public AsyncTask(@Nullable Looper callbackLooper) { mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { //After execution, issue results postResult(result); } return result; } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } private static Handler getMainHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { //The looper of the main thread is passed in here, so it is used to cut messages to the main thread sHandler = new InternalHandler(Looper.getMainLooper()); } return sHandler; } }
You can see that the InternalHandler instance is first created using the Looper of the main thread. Then an instance mWorker of WorkerRunnable is created. In the call method, doInBackground(mParams) is called. It can be assumed that the call method performs the online process pool. Then an instance of FutureTask, mFuture, is created and passed in to mWorker. How can mFuture be used? It will be analyzed later. Let's first look at the implementation of the Handler InternalHandler:
private static class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); } @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }
See the messages that process the sending result and the sending progress. Where did the news come from? Leave a question first. Continue to see the execute method of AsyncTask:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; /** * Serial actuator */ public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); @MainThread public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } @MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }
The execute method goes to the executeOnExecutor method. First, judge the current task status. The default is the PENDING status of the task to be executed, and then it changes to RUNNING. But if the RUNNING in progress and FINISHED finished FINISHED finished FINISHED, an exception will be thrown. This is also the reason that a task instance can only be executed once. Then it goes to onpre execute(), because execute is executed in UI thread, so it also explains why it is executed in UI thread. Then assign the parameter to mWorker, and mFuture executes the static execution () method of sdefaulteexecutor as the parameter. Note that sdefaulteexecutor is a SerialExecutor instance. To see:
//Thread pool public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; //The execute method is locked public synchronized void execute(final Runnable r) { //Put r at the end of the task queue mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { //When the task is finished, the next one will be executed scheduleNext(); } } }); //After r is put into the task queue, the tasks in the queue head will be executed if there are no currently fetched tasks if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { //Take the task execution of the queue head if ((mActive = mTasks.poll()) != null) { //Thread pool executor is a thread pool THREAD_POOL_EXECUTOR.execute(mActive); } } }
There are comments above. It can be seen that the serial executor is the serial executor, and the final execution is in the thread pool of thread pool executor. r.run() actually uses the run method of FutureTask:
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } public void run() { if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { //call method of callable result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
The call() method of the incoming callable called in the run method of FutureTask, combined with the construction method of the above AsyncTask, mWorker is the call() method for realizing callable. So the doInBackground method in it will execute in the process pool serially. Because of serialization, the use of execute method can't execute the task that is particularly time-consuming, otherwise it will block the task waiting later. If you want to parallelize, you can use the executeOnExecutor method of asynctask and pass it into thread pool thread pool executor.
It is also noted that after doInBackground is executed, postResult(result) is called, and result is doInBackground return value:
private Handler getHandler() { return mHandler; } private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
You can see that when using handler to send messages, the message type is message ﹣ post ﹣ result. As we saw earlier, internal handleMessage of InternalHandler is processed, that is, calling the finish method of task:
private void finish(Result result) { if (isCancelled()) { //If the task is cancelled, call onCancelled onCancelled(result); } else { //No cancellation onPostExecute(result); } //Modify task status to complete mStatus = Status.FINISHED; }
It can be seen that if cancel() is not called, onPostExecute will be executed, so onPostExecute is also executed in UI thread.
Finally, let's look at the publishProgress method:
protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } }
If the task is not cancelled, the handler is also used, and the message type is message ﹣ post ﹣ process. As we saw earlier, the internal handleMessage of InternalHandler is processed. Finally, the onProgressUpdate method is executed in the UI thread.
Two examples
Example 1, default serial execution:
new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { Log.i(TAG, "task1 SERIAL_EXECUTOR doInBackground: "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { Log.i(TAG, "task2 SERIAL_EXECUTOR doInBackground: "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { Log.i(TAG, "task3 SERIAL_EXECUTOR doInBackground: "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.execute();
The execution result is as follows. It is printed every two seconds. It can be seen that the execution is serial.
2020-01-16 14:51:40.836 13346-13599/com.hfy.demo01 I/hfy: task1 SERIAL_EXECUTOR doInBackground: 2020-01-16 14:51:42.876 13346-13598/com.hfy.demo01 I/hfy: task2 SERIAL_EXECUTOR doInBackground: 2020-01-16 14:51:44.915 13346-13599/com.hfy.demo01 I/hfy: task3 SERIAL_EXECUTOR doInBackground:
Example 2, parallel execution - using thread pool directly:
new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { Log.i(TAG, "task1 THREAD_POOL_EXECUTOR doInBackground: "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { Log.i(TAG, "task2 THREAD_POOL_EXECUTOR doInBackground: "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { Log.i(TAG, "task3 THREAD_POOL_EXECUTOR doInBackground: "); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
The execution results are as follows, and printed out at the same time. It can be seen that the execution is in parallel.
2020-01-16 14:51:38.772 13346-13599/com.hfy.demo01 I/hfy: task1 THREAD_POOL_EXECUTOR doInBackground: 2020-01-16 14:51:38.773 13346-13600/com.hfy.demo01 I/hfy: task2 THREAD_POOL_EXECUTOR doInBackground: 2020-01-16 14:51:38.774 13346-13601/com.hfy.demo01 I/hfy: task3 THREAD_POOL_EXECUTOR doInBackground:
To sum up, AsyncTask uses serial executors to execute tasks in the online process pool by default. We can also use executeOnExecutor to execute tasks in parallel using the thread pool directly. Internally, the handler is used to switch the progress and results from the thread pool to the UI thread.
1.2 HandlerThread
HandlerThread inherits from thread, with loop prepared internally and loop opened. Therefore, the UI thread can use handler to send tasks to the HandlerThread for execution, and the tasks can be sent multiple times at will. (the time-consuming operation in the run method is finished when the normal thread finishes executing.) When not in use, such as onDestroy, use quit() or quitsafety() to exit.
public class HandlerThread extends Thread { int mPriority; int mTid = -1; Looper mLooper; private @Nullable Handler mHandler; public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; } //Something can be done before looper is turned on protected void onLooperPrepared() { } @Override public void run() { mTid = Process.myTid(); //Prepare Looper instance for current thread Looper.prepare(); //Lock to ensure that the looer instance can be obtained synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); //Open cycle Looper.loop(); mTid = -1; } public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { //If you can't get it, wait for notifyAll() in run to be called wait(); } catch (InterruptedException e) { } } } return mLooper; } //immediate withdrawal public boolean quit() { Looper looper = getLooper(); if (looper != null) { looper.quit(); return true; } return false; } //Safe exit (exit after performing the task in progress) public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { looper.quitSafely(); return true; } return false; } public int getThreadId() { return mTid; } }
For example
private void testHandlerThread() { HandlerThread handlerThread = new HandlerThread("HandlerThreadName"); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1000: try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.i(TAG, "handleMessage: thread name="+Thread.currentThread().getName()+",what="+msg.what); break; case 1001: try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Log.i(TAG, "handleMessage: thread name="+Thread.currentThread().getName()+",what="+msg.what); break; default: break; } } }; Log.i(TAG, "sendMessage thread name="+Thread.currentThread().getName()); handler.sendMessage(Message.obtain(handler, 1000)); handler.sendMessage(Message.obtain(handler, 1001)); }
It can be seen that the main thread sends two tasks, which are executed in order in HandlerThread.
2020-01-16 17:12:46.832 16293-16293/com.hfy.demo01 I/hfy: sendMessage thread name=main 2020-01-16 17:12:48.833 16293-17187/com.hfy.demo01 I/hfy: handleMessage: thread name=HandlerThreadName,what=1000 2020-01-16 17:12:49.834 16293-17187/com.hfy.demo01 I/hfy: handleMessage: thread name=HandlerThreadName,what=1001
1.3 IntentService
IntentService is an abstract class inherited from Service, which can perform background time-consuming tasks and automatically stop after execution.
Because it is a Service, that is, a component of Android, which has a higher priority than a single thread and is not easy to be killed by the system, it can be used to perform high priority background tasks,
public abstract class IntentService extends Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; private boolean mRedelivery; //Inner class handler private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { //Call back first, and wait for the IntentService subclass to override its own logic onHandleIntent((Intent)msg.obj); //To end the service itself, msg.arg1 is the Id of a startService. //But if startService is called externally at this time, the latest request id is not msg.arg1, so the following sentence will not end the service. stopSelf(msg.arg1); } } //Name is the thread name public IntentService(String name) { super(); mName = name; } ... @Override public void onCreate() { super.onCreate(); //Create HandlerThread instance and start HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); //Create the handler corresponding to looper, so the handleMessage of mServiceHandler is executed in the thread mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(@Nullable Intent intent, int startId) { //Send a message with the parameters startId and intent //Every time startService() goes here Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } @Override public void onDestroy() { //Exit looper loop mServiceLooper.quit(); } ... //The executor is in the thread, and there is only one intent (because of the looper's queue). If this method takes too long to execute, it will block other requests. After all requests are executed, the service will stop automatically. Therefore, stopSelf cannot be called manually. @WorkerThread protected abstract void onHandleIntent(@Nullable Intent intent); }
onCreate creates the HandlerThread instance and the corresponding Handler instance mServiceHandler, so the tasks sent by mServiceHandler will be executed in the thread.
OnStart is invoked in onStartCommand, and mServiceHandler is sent by onStart in real time. The parameters are startId and intent. StartId is the markup to start service every time. Intent is the intent to start service.
Exit looper loop in onDestroy.
Where are the messages sent processed?
It depends on ServiceHandler, which inherits from Handler. In handleMessage method, the abstract method onHandleIntent((Intent)msg.obj) is called first. The parameter is the intent of starting service. So the subclass of IntentService must override onHandleIntent and handle the intent. Because mServiceHandler gets the looper of HandlerThread, the onHandleIntent() is executed in the child thread.
Then we call stopSelf(msg.arg1), MSG. Arg1, which is the flag to start the service. The description of stopSelf(int startId) is as follows:
startId: represents the number of times to start the service, which is generated by the system.
stopSelf(int startId): the service will be stopped only when its parameter startId is equal to the ID generated when the service is last started.
stopSelf(): stop the service directly.
Usage scenario: if there are multiple service startup requests sent to onStartCommand() at the same time, you should not call stopSelf() after processing a request; because before calling this function to destroy service, service may receive a new startup request, if the service is destroyed, the new request will not be processed. In this case, stopSelf(int startId) should be called.
Therefore, when the service is started multiple times, onStart will be called multiple times, and multiple tasks will be issued. When each task finishes executing onHandleIntent, it will be judged in stopSelf(int startId). If the service is started again, it will not stop. Then looper continues to remove the message and continue processing. It stops until the startId in stopSelf is the same as the latest startId. Because it is looper, these tasks are executed in the order of starting service.
For example
private void testIntentService() { Intent intent= new Intent(this, MyIntentService.class); intent.putExtra("task_name","task1"); startService(intent); intent.putExtra("task_name","task2"); startService(intent); intent.putExtra("task_name","task3"); startService(intent); } public static class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentServiceThread"); } @Override protected void onHandleIntent(Intent intent) { Log.i(TAG, "MyIntentService onHandleIntent: begin."+intent.getStringExtra("task_name")); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.i(TAG, "MyIntentService onHandleIntent: done."+intent.getStringExtra("task_name")); } @Override public void onDestroy() { Log.i(TAG, "MyIntentService onDestroy: "); super.onDestroy(); } }
2020-01-17 09:58:44.639 11117-11236/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: begin.task1 2020-01-17 09:58:46.640 11117-11236/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: done.task1 2020-01-17 09:58:46.641 11117-11236/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: begin.task2 2020-01-17 09:58:48.642 11117-11236/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: done.task2 2020-01-17 09:58:48.644 11117-11236/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: begin.task3 2020-01-17 09:58:50.645 11117-11236/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: done.task3 2020-01-17 09:58:50.650 11117-11117/com.hfy.demo01 I/hfy: MyIntentService onDestroy:
The static internal class MyIntentService inherits the IntentService and rewrites onHandleIntent, that is, sleeping for two seconds. Then it starts three times continuously. It can be seen from the log that it is executed in sequence. Finally, it goes to onDestroy after the execution.
Again, if it is sent every three seconds:
private void testIntentService() { Log.i(TAG, "testIntentService: task1"); Intent intent= new Intent(this, MyIntentService.class); intent.putExtra("task_name","task1"); startService(intent); new Handler().postDelayed(new Runnable() { @Override public void run() { Log.i(TAG, "testIntentService: task2"); intent.putExtra("task_name","task2"); startService(intent); } }, 3000); new Handler().postDelayed(new Runnable() { @Override public void run() { Log.i(TAG, "testIntentService: task3"); intent.putExtra("task_name","task3"); startService(intent); } }, 3000); }
2020-01-17 10:16:29.335 14739-14739/com.hfy.demo01 I/hfy: testIntentService: task1 2020-01-17 10:16:29.698 14739-14843/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: begin.task1 2020-01-17 10:16:31.698 14739-14843/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: done.task1 2020-01-17 10:16:31.701 14739-14739/com.hfy.demo01 I/hfy: MyIntentService onDestroy: 2020-01-17 10:16:32.371 14739-14739/com.hfy.demo01 I/hfy: testIntentService: task2 2020-01-17 10:16:32.390 14739-14862/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: begin.task2 2020-01-17 10:16:34.391 14739-14862/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: done.task2 2020-01-17 10:16:34.451 14739-14739/com.hfy.demo01 I/hfy: MyIntentService onDestroy: 2020-01-17 10:16:35.339 14739-14739/com.hfy.demo01 I/hfy: testIntentService: task3 2020-01-17 10:16:35.364 14739-14873/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: begin.task3 2020-01-17 10:16:37.364 14739-14873/com.hfy.demo01 I/hfy: MyIntentService onHandleIntent: done.task3 2020-01-17 10:16:37.367 14739-14739/com.hfy.demo01 I/hfy: MyIntentService onDestroy:
It can be seen that once each task is completed, onDestroy will go. This is because when a task finishes executing and goes to stopSelf(int startId), the service has not been opened again, so the startId in stopSelf at this time is the latest, so the service will be stopped.
2, Thread pool in Android
The advantages of thread pool are as follows:
- It can reuse the threads in the thread pool and avoid the performance overhead caused by the creation and destruction of threads.
- It can effectively control the maximum concurrent number in the thread pool and avoid the blocking phenomenon caused by a large number of threads occupying system resources.
- It can manage the thread simply, and provide the function of timing execution and specifying interval cycle execution.
The thread pool in Android comes from the Java Executor, and the implementation is ThreadPoolExecutor.
2.1 ThreadPoolExecutor
ThreadPoolExecutor is the real implementation of thread pool. Look at the parameters in its construction method, which will affect the functional characteristics of thread pool.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
- corePoolSize, the number of core threads. By default, the core threads will survive in the thread pool, even if they are idle. But when allowCoreThreadTimeOut is set to true, the core thread will have an idle timeout, and the idle timeout will be terminated. The timeout is specified by keepAliveTime and unit.
- Maximum poolsize, the maximum number of threads. When the active thread reaches this number, subsequent new tasks will be blocked.
- keepAliveTime, the timeout when a non core thread is idle. Non core threads will be recycled after idle time. When allowCoreThreadTimeOut is set to true, keepAliveTime also works on the core thread.
- Unit is the time unit of keepAliveTime. The value is enumeration, including TimeUnit.MINUTES, TimeUnit.SECONDS, TimeUnit.MILLISECONDS, etc.
- workQueue, the task queue in the thread pool. The Runnable submitted through the execute method of the thread pool will exist in this queue.
- threadFactory, a thread factory, provides the ability to create new threads for the thread pool.
There is also a less commonly used parameter RejectedExecutionHandler handler, which calls its rejectedExecution() method to handle the situation that a new task cannot be executed when the task queue is full. The default implementation of handler is AbortPolicy, and an exception RejectedExecutionException will be thrown directly in rejectedExecution(). Other implementations like DiscardPolicy's rejectedexecution () do nothing. There are also CallerRunsPolicy and DiscardOldestPolicy.
The execution rules of ThreadPoolExecutor are as follows:
- If the number of threads in the thread pool does not reach the number of core threads, a core thread will be started directly to perform this task. (whether the started core thread is idle or not)
- If the number of threads in the thread pool has reached or exceeded the number of core threads, the task is inserted into the task queue and queued for execution.
- If task 2 cannot be inserted into the queue, the queue is usually full. If the maximum number of threads is not reached at this time, non core threads will be started to execute this task.
- If the maximum number of threads in 3 is reached, the task execution will be rejected, that is, the rejectedExecution() of RejectedExecutionHandler will be called to notify the caller.
Let's look at the configuration of the AsyncTask process pool:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); // We want at least 2 threads and at most 4 threads in the core pool, // preferring to have 1 less than the CPU count to avoid saturating // the CPU with background work private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE_SECONDS = 30; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; }
It can be seen that the configuration of thread pool executor is as follows:
- Number of core threads, 2-4
- Maximum number of threads, number of CPU cores * 2 + 1
- Timeout 30s, allow core thread to timeout
- Queue capacity 128
2.2 classification of thread pool
Four types of thread pools commonly used in Android are directly or indirectly configured with ThreadPoolExecutor to realize their own characteristics. They are FixedThreadPool, CachedThreadPool, ScheduledThreadPool, singlethreadexecution. They are all available through the tool class Executors.
2.2.1 FixedThreadPool
Obtained through the newFixedThreadPool method of Executors. Fixed number of core threads, no non core threads, no recycling when idle, unlimited queue length. Because it will not be recycled, it can quickly execute external requests.
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
2.2.2 CachedThreadPool
Obtained through the newCachedThreadPool method of Executors. The number of core threads is 0, the number of non core threads is unlimited, the idle recovery timeout is 60s, and the queue cannot be inserted into the task. When all threads are active, a new thread will be created to process the task, otherwise the idle thread will be used to process the task. When the whole thread pool is idle, all threads will be recycled without taking up system resources. Therefore, it is suitable for performing a large number of less time-consuming tasks
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
2.2.3 ScheduledThreadPool
Obtained through the newScheduledThreadPool method of Executors. The fixed number of core threads does not limit the number of non core threads. The idle recovery timeout of non core threads is 10ms. It is generally used to perform scheduled tasks and repetitive tasks with fixed cycle.
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } private static final long DEFAULT_KEEPALIVE_MILLIS = 10L; public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
2.2.4 SingleThreadExecutor
Obtained through the newSingleThreadExecutor method of Executors. There is only one core thread, which will not be recycled. It can ensure that all tasks are executed in order without dealing with thread synchronization.
private void testThreadPoolExecutor() { Runnable runnable = new Runnable() { @Override public void run() { try { Log.i(TAG, "testThreadPoolExecutor: run begin"); Thread.sleep(4000); Log.i(TAG, "testThreadPoolExecutor: run end"); } catch (InterruptedException e) { e.printStackTrace(); } } }; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4); fixedThreadPool.execute(runnable); ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); cachedThreadPool.execute(runnable); ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4); scheduledThreadPool.execute(runnable); //Delay execution by 2 seconds scheduledThreadPool.schedule(runnable, 2, TimeUnit.SECONDS); //Delay the execution for 2 seconds, and then count the time at the beginning of each task. After 1 second, if the task is finished, execute the next time immediately; if it is not finished, execute the next time immediately after it is finished. scheduledThreadPool.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS); //Delay execution for 3 seconds, and then count by the time after each task is executed. After 2 seconds, execute the next time~ scheduledThreadPool.scheduleWithFixedDelay(runnable,1,2,TimeUnit.SECONDS); ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); singleThreadExecutor.execute(runnable); }
Note: when using scheduledThreadPool, pay attention to the logical differences between the circular tasks of scheduleAtFixedRate and scheduleWithFixedDelay:
- scheduleAtFixedRate: it is timed by the start time of each task. After the period time, if the task is finished, execute the next time immediately; if it is not finished, execute the next time immediately after it is finished.
- scheduleWithFixedDelay: it is timed by the time after each task is executed. After the period time, it is executed next time.
In addition, both of these methods are executed next time after the task is finished. If a task fails to be executed due to some difference, the whole cycle task will fail. So we need to add a timeout mechanism to the task (for example, add try catch finally to the task, and catch the timeout exception) to ensure that the task can end even if an exception occurs, so as to ensure the normal execution of the cycle.