In addition to threads, There are other roles of threads in Android: AsyncTask, HandlerThread, IntentService.

  • AsyncTask: Encapsulates a thread pool and a handler internally to update the UI in child threads.
  • HandlerThread: A thread that can use a message loop, within which a Handler can be used.
  • IntentService: HandlerThread is used to execute tasks internally. The task is automatically terminated after completion. As a component, it has a higher priority and is not easy to kill.

Thread is the smallest unit of operating system scheduling, is a limited resource, can not be produced without limit. And there is an overhead associated with the creation and destruction of threads. If there are a large number of threads, the system will schedule the threads through time slice rotation. Therefore, threads cannot be parallel unless the number of threads is smaller than or equal to the number of cpus. So you need a thread pool, which can cache a certain number of threads and avoid the overhead of frequent thread creation and destruction.

Thread form in Android

1.1 AsyncTask

AsyncTask is used to process asynchronous tasks in a thread pool and send progress and results to the UI thread.

1.1.1 Usage

The basic usage of AsyncTask is as follows:

    private void testAsyncTask(a) {
        // Instantiate in the main thread. (in 9.0, the child thread creates instance and then the main thread executes.)
        // Three generic parameters represent the parameter type, the progress type, and the result type in sequence.
        // The overridden methods cannot be called directly
        AsyncTask<Integer, Integer, String> task = new AsyncTask<Integer, Integer, String>() {

            @Override
            protected void onPreExecute(a) {
                super.onPreExecute();
                // The main thread executes, before the asynchronous task
                Log.i(TAG, "testAsyncTask onPreExecute: ");
            }

            @Override
            protected String doInBackground(Integer... integers) {
                Log.i(TAG, "testAsyncTask doInBackground: ");
                // Tasks perform time-consuming operations in a thread pool
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Send progress
                publishProgress(50);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // send the progress again
                publishProgress(100);

                return "I am the result. Parameter is" + integers[0];
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
                // Execute on the main thread, after the asynchronous task has finished
                Log.i(TAG, "testAsyncTask onPostExecute: "+s);
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                super.onProgressUpdate(values);
                // Execution is performed on the main thread after calling publishProgress()
                Log.i(TAG, "TestAsyncTask onProgressUpdate: Progress:"+values[0] +"%");
            }

            @Override
            protected void onCancelled(a) {
                super.onCancelled();
                // Cancel the task}};// Execute must be executed on the main thread only once
        task.execute(100);
    }
Copy the code

The 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: progress:50%
2020-01-14 11:29:05.589 13209-13209/com.hfy.demo01 I/hfy: testAsyncTask onProgressUpdate: progress:100%
2020-01-14 11:29:05.590 13209-13209/com.hfy.demo01 I/hfy: testAsyncTask onPostExecute: I am the result. The parameter is100
Copy the code

1.1.2 Principle Analysis:

Let’s start with the constructor

    public AsyncTask(a) {
        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(a) 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 {
                	// When executed, send the result
                    postResult(result);
                }
                returnresult; }}; mFuture =new FutureTask<Result>(mWorker) {
            @Override
            protected void done(a) {
                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(a) {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
            	// Looper is passed to the main thread, so it is used to cut messages to the main thread
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            returnsHandler; }}Copy the code

You see that an instance of InternalHandler is first created using the Looper of the main thread. MWorker = WorkerRunnable (); mParams = doInBackground(); It then creates an instance of FutureTask called mFuture and passes it to mWorker. How does mFuture work? And then it says. Let’s look at the implementation of 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; }}}Copy the code

A message indicating the sending result and progress is displayed. Where did it come from? Leave a question. Continue with the AsyncTask execute method:

    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;
    }
Copy the code

The execute method moves to the executeOnExecutor method, which checks the current task status. By default, the task status is PENDING and then changes to RUNNING. However, if RUNNING and FINISHED are executed, exceptions will be thrown. This is why a task instance can only be executed once. And then onPreExecute(), because execute is executed on the UI thread, which explains why it’s executed on the UI thread. The argument is then assigned to the mWorker, and mFuture executes the static execute() method of sDefaultExecutor as an argument. Note that sDefaultExecutor is a SerialExecutor instance.

	/ / 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) {
        	// store r to the end of the task queue
            mTasks.offer(new Runnable() {
                public void run(a) {
                    try {
                        r.run();
                    } finally {
                    	// Execute the next taskscheduleNext(); }}});// Put r into the task queue, then take the task at the head of the queue to execute
            if (mActive == null) { scheduleNext(); }}protected synchronized void scheduleNext(a) {
        	// Execute the task in the queue head
            if((mActive = mTasks.poll()) ! =null) {
            	//THREAD_POOL_EXECUTOR is a thread poolTHREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

SerialExecutor is a SerialExecutor that is executed in the thread pool of THREAD_POOL_EXECUTOR. R.run () actually goes to FutureTask’s run method:

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
    public void run(a) {
        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); }}Copy the code

Combined with the AsyncTask constructor above, mWorker is the call() method that implements callable. So the doInBackground method is executed serially in the thread pool. Because of serialization, the execute method cannot execute particularly time-consuming tasks, otherwise it will block waiting tasks later. To parallelize, use the executeOnExecutor method of AsyncTask, passing THREAD_POOL_EXECUTOR.

PostResult (result) is the value returned by doInBackground:

    private Handler getHandler(a) {
        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;
    }
Copy the code

InternalHandler (MESSAGE_POST_RESULT) InternalHandler (MESSAGE_POST_RESULT)

    private void finish(Result result) {
        if (isCancelled()) {
        	// If the task is cancelled, call onCancelled
            onCancelled(result);
        } else {
        	// There is no cancellation
            onPostExecute(result);
        }
        // Change the task status to Completed
        mStatus = Status.FINISHED;
    }
Copy the code

So if you don’t call Cancel (), you’re going to go onPostExecute, so onPostExecute is also going to be executed on the UI thread.

Finally, look at the publishProgress method:

    protected final void publishProgress(Progress... values) {
        if(! isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS,new AsyncTaskResult<Progress>(this, values)).sendToTarget(); }}Copy the code

If the task is not canceled, the handler is also used, and the message type is MESSAGE_POST_PROGRESS. As you can see from InternalHandler, the handleMessage is handled internally, and the onProgressUpdate method is executed on the UI thread.

To take two examples 🌰 Example 1, the 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();
Copy the code

The result is printed every two seconds in serial order.

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: 
Copy the code

Example 2, parallel execution – directly using a thread pool:

        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);
Copy the code

The execution result is printed as follows, indicating that the execution is 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: 
Copy the code

To summarize, AsyncTask internally uses a serial executor to execute tasks in a thread pool serically by default, or we can use executeOnExecutor to execute tasks directly in parallel using the thread pool. Internally, a handler is used to switch progress and results from the thread pool to the UI thread.

1.2 HandlerThread

HandlerThread inherits from Thread and has Looper prepared and looping started internally. So you can send tasks from the UI thread to the HandlerThread using a handler, and you can send tasks as many times as you want. (Normal threads, on the other hand, end up executing time-consuming operations in the run method.) When not used, for example, in onDestroy, use quit() or quitSafely() to quit.

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;
    }
    // There are a few things you can do before you start looper
    protected void onLooperPrepared(a) {}@Override
    public void run(a) {
        mTid = Process.myTid();
        // Prepare Looper instances for the current thread
        Looper.prepare();
        // Lock the looer instance
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        // Start the loop
        Looper.loop();
        mTid = -1;
    }
   
    public Looper getLooper(a) {
        if(! isAlive()) {return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                	// Wait for notifyAll() in run to be called
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
	// Exit directly
    public boolean quit(a) {
        Looper looper = getLooper();
        if(looper ! =null) {
            looper.quit();
            return true;
        }
        return false;
    }
    // Exit safely (exit after executing the task in progress)
    public boolean quitSafely(a) {
        Looper looper = getLooper();
        if(looper ! =null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    public int getThreadId(a) {
        returnmTid; }}Copy the code

For example, 🌰

    private void testHandlerThread(a) {
        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()+"I ="+msg.what);
                        break;
                    case 1001:
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.i(TAG, "handleMessage: thread name="+Thread.currentThread().getName()+"I ="+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));
    }
Copy the code

You can see that two tasks are sent in the main thread and executed in sequence in the 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
Copy the code

1.3 IntentService

IntentService is an abstract class inherited from Service. It can execute time-consuming tasks in the background and automatically stop them after execution. A Service is a component of Android that has a higher priority than a simple thread and is not easily killed by the system. Therefore, it can be used to perform background tasks with a higher priority.

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 the IntentService subclass to override its logic
            onHandleIntent((Intent)msg.obj);
            Arg1 is the Id of startService.
            If startService is called, the latest request id is not msg.arg1, so the following statement does not terminate the service.stopSelf(msg.arg1); }}//name is the thread name
    public IntentService(String name) {
        super();
        mName = name;
    }
    ...
    @Override
    public void onCreate(a) {
        super.onCreate();
        // Create the HandlerThread instance and start it
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
		// Create handler for looper, so mServiceHandler's handleMessage executes 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
    	// startService() goes here every time
        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(a) {
    	// Exit looper loopmServiceLooper.quit(); }...// There is only one intent (because of looper's queue), so if this method takes too long to execute, it will block other requests. After all the requests are executed, the service will stop automatically, so you cannot manually call stopSelf.
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}
Copy the code

OnCreate creates an instance of HandlerThread, corresponding to Handler instance mServiceHandler, so tasks sent by mServiceHandler will be executed in the thread. OnStartCommand calls onStart. OnStart does use mServiceHandler to send a message with the parameters startId and intent. An intent is an intent that starts a service. Exit looper loop in onDestroy.

Where are sent messages processed? The handleMessage method first calls the abstract onHandleIntent((Intent)msg.obj). The parameter is the Intent that starts the service. So a subclass of IntentService must override onHandleIntent and handle that intent. Since the mServiceHandler gets the Looper of the HandlerThread, onHandleIntent() is executed in the child thread. Next, stopSelf(msg.arg1) is called. Msg.arg1 is used to start the service. StopSelf (int startId)

StartId: indicates the number of times for starting the service. The value is generated by the system. StopSelf (int startId) : The service is stopped only when the parameter startId is the same as the ID generated when the service is started.

StopSelf () : Stops the service directly.

Usage scenario: If multiple service start requests are sent to onStartCommand() at the same time, stopSelf() should not be called after processing one request; If the service is destroyed, the new request will not be processed. In this case stopSelf(int startId) should be called.

If onHandleIntent is executed, the onStart task will not stop. If onHandleIntent is executed, the onStart task will not stop. Looper then moves on to the next message and continues processing. It will stop until the startId in stopSelf is the same as the newly started startId. Since it is Looper, these tasks are executed in the order in which services are started.

For example, 🌰

    private void testIntentService(a) {

        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(a) {
            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(a) {
            Log.i(TAG, "MyIntentService onDestroy: ");
            super.onDestroy(); }}Copy the code
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:
Copy the code

The static inner class MyIntentService inherits IntentService and overwrites onHandleIntent for two seconds. Start onDestroy three times in a row and execute onDestroy in sequence.

Now, what if you send it three seconds apart:

        private void testIntentService(a) {

        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(a) {
                Log.i(TAG, "testIntentService: task2");
                intent.putExtra("task_name"."task2"); startService(intent); }},3000);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run(a) {
                Log.i(TAG, "testIntentService: task3");
                intent.putExtra("task_name"."task3"); startService(intent); }},3000);
    }
Copy the code
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:
Copy the code

After each task is completed, onDestroy is destroyed. This is because when a task is finished, the service has not been started again, so the startId in stopSelf is the latest, so the service will be stopped.

Thread pools in Android

The advantages of thread pools are as follows:

  • The ability to reuse threads in a thread pool avoids the performance overhead of thread creation and destruction.
  • It can effectively control the maximum number of concurrent threads in the thread pool and avoid the blocking phenomenon caused by the mutual preemption of system resources between a large number of threads.
  • Thread can be simple management, and provide periodic execution, specified interval cycle execution function. Thread pools in Android are derived from Java executors and are being implemented as ThreadPoolExecutor.

2.1 ThreadPoolExecutor

ThreadPoolExecutor is the real implementation of a thread pool. Look at the parameters in its constructor, which affect the functionality of the 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);
    }
Copy the code
  • CorePoolSize, the number of core threads. By default, the core thread will always be alive in the thread pool, even in idle state. However, when allowCoreThreadTimeOut is set to true, the core thread will have an idle timeout and will terminate if idle exceeds the timeout. The timeout period is specified by keepAliveTime and unit.
  • MaximumPoolSize, the maximum number of active threads at which new tasks will be blocked.
  • KeepAliveTime: Specifies the timeout period when a non-core thread is idle. Non-core threads that are idle for more than this time are reclaimed. When allowCoreThreadTimeOut is set to true, keepAliveTime also applies to the core thread.
  • Unit is the unit of keepAliveTime. The value is an enumeration, including timeunit. MINUTES, timeunit. SECONDS, and timeunit. MILLISECONDS.
  • WorkQueue, a queue of tasks in a thread pool in which Runnable submissions via the execute method of the thread pool are stored.
  • ThreadFactory, a threadFactory, provides thread pools with the ability to create new threads.

There is also a less commonly used argument, RejectedExecutionHandler, which calls its rejectedExecution() method to handle the situation where a new task cannot be executed when the task queue is full. Handler is the default implementation AbortPolicy, rejectedExecution () throws an exception directly RejectedExecutionException. Other implementations such as rejectedExecution() in DiscardPolicy do nothing. CallerRunsPolicy and DiscardOldestPolicy.

ThreadPoolExecutor executes tasks according to the following rules:

  • If the number of threads in the thread pool does not reach the number of core threads, a core thread is directly started to perform the task. (Regardless of whether the started core thread is idle)
  • If the number of threads in the thread pool reaches or exceeds the number of core threads, the task is inserted into the task queue and queued for execution.
  • If the task in 2 cannot be inserted into the queue, the queue is usually full. If the maximum number of threads has not been reached, the non-core thread will be started to execute the task.
  • If the number of threads in 3 reaches the maximum number, the task is rejected and the rejectedExecution() of RejectedExecutionHandler is called to notify the caller.

Let’s take a look at AsyncTask thread pool configuration:

    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;
    }
Copy the code

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
  • The timeout period is 30s, allowing the core thread to timeout
  • Queue capacity 128

2.2 Classification of thread pools

The four common types of thread pools in Android are either directly or indirectly configured with ThreadPoolExecutor to implement their own features. These are FixedThreadPool, CachedThreadPool, ScheduledThreadPool, and SingleThreadExecutor. They can be obtained by using the tool Executors.

2.2.1 FixedThreadPool

Obtain the file by using the newFixedThreadPool method of Executors. Fixed number of core threads, no non-core threads, idle will not be reclaimed, queue length unlimited. External requests can be executed quickly because they are not recycled.

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
Copy the code

2.2.2 CachedThreadPool

Obtain the file by following the Executors newCachedThreadPool method. The number of core threads is 0, the number of non-core threads is unlimited, the idle recycling timeout time is 60 seconds, and the queue cannot insert tasks. When all threads are active, new threads are created to process tasks, otherwise idle threads are used to process tasks. When the entire thread pool is idle, all threads are reclaimed without consuming system resources. Therefore, it is suitable for performing a large number of tasks that are less time-consuming

    public static ExecutorService newCachedThreadPool(a) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
Copy the code

2.2.3 ScheduledThreadPool

Obtain the information by using the newScheduledThreadPool method of Executors. Fixed number of core threads, no limit on the number of non-core threads, non-core threads idle recovery timeout is 10ms. It is used to perform scheduled tasks and repetitive tasks at a fixed period.

    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());
    }
Copy the code

2.2.4 SingleThreadExecutor

Obtain the file by using the newSingleThreadExecutor method. Only 1 core thread, no collection. This ensures that all tasks are executed sequentially without having to deal with thread synchronization.

    private void testThreadPoolExecutor(a) {
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                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 execution for 2 seconds, and then time with the start time of each task. After 1 second, if the task is finished, execute the next task immediately; If it does not end, wait for it to end immediately after the next execution.
        scheduledThreadPool.scheduleAtFixedRate(runnable, 0.1, TimeUnit.SECONDS);
        // Delay the execution for 3 seconds, and then count the time after each task is executed. After 2 seconds, execute the next ~
        scheduledThreadPool.scheduleWithFixedDelay(runnable,1.2,TimeUnit.SECONDS);

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(runnable);
    }
Copy the code

Note: When using scheduledThreadPool, note the logical difference between scheduleAtFixedRate and scheduleWithFixedDelay.

  • ScheduleAtFixedRate: indicates the time when each task starts. After this time, if the task is complete, the next time is immediately executed. If it does not end, wait for it to end immediately after the next execution.
  • ScheduleWithFixedDelay: indicates the time after each task is executed. After the period, the next task is executed.

In addition, the two methods are executed after the task is finished, so if a task cannot be executed, the whole loop task will be invalid. Therefore, it is necessary to add a timeout mechanism (such as try-catch-finally to the task to catch timeout exceptions) to ensure that the task can end even if an exception occurs, so as to ensure the normal execution of the cycle.

If you like this article or think it is well written, please help to like, bookmark and share it. Thanks!

Welcome to my public account: