Detailed explanation of Android message mechanism (source code analysis ThreadLocal, Looper, Handler, AsyncTask)

Android message mechanism

1 Overview

The message mechanism of Android mainly refers to the running mechanism of Handler and the working process of MessageQueue and Looper attached to Handler.

Android applications are event driven, and each event will be transformed into a system Message, namely Message. The Message contains information about the event and the Handler of the Message. Each process has a default Message Queue (Message Queue, which stores the Message list in the form of a single linked list). This Message Queue maintains a list of messages to be processed. The Message cycle continuously takes messages from this queue and processes messages, which makes the application operate dynamically.

The working principle of Message, MessageQueue, looper and Handler is like the production line of a factory. Looper is the engine, MessageQueue is the conveyor belt, Handler is the worker and Message is the product to be processed.

Messages are delivered to the message queue through the Handler. The message queue is in the loop associated with the Handler. After the message cycle is started, messages will be continuously obtained from the queue. Message processing is divided into Native layer and Java layer. The two layers have their own message mechanism. The Native layer is based on pipeline and epoll, while the Java layer is an ordinary linked list. After getting the message, the callback of the message or the handleMessage() distributed to the corresponding Handler will be called for processing. After the message is processed, it will be received by the message pool for next utilization.

The loop created by the Handler will use the Looper of the current thread to construct the message system. The Handler uses ThreadLocal internally to get the Looper of the current thread. ThreadLocal can store and provide data in different threads without interfering with each other. The thread does not have a Looper by default. If you use a Handler, you must create a Looper for the thread.

The main function of Handler is to switch a task to a specified thread for execution. This is because Android stipulates that the UI can only be accessed in the thread that created it (in most cases, the UI is created from the UI thread), otherwise an exception will be thrown( The checkThread() of ViewRootImpl verifies the UI operation: whether the operation thread of the UI control is the thread that created it)

Why does the system not allow UI access in child threads?

Because the UI control of android is not thread safe, concurrent access in multiple threads may cause the UI control to be in an unexpected state.

Why not lock the access of UI controls? Disadvantages:

  • Complicate the logic of UI access
  • Reducing the efficiency of UI access will block the execution of some threads

Therefore, it is the simplest and most efficient to handle the access of UI controls with a single thread.

Use Handler: create a Looper for the current thread or on a thread with a Looper.

  • post(Runnable r) wrap runnable in Message
  • sendMessage() (post ultimately calls send)

When send() is called, enqueueMessage of MessageQueue will be called to put the message into the message queue. Looper will handle new messages when he finds them. Finally, Runnable or Handler's handleMessage() will be called.

ThreadLocal

ThreadLocal, also known as thread local variable. Is a data storage class within a thread, which can store and obtain data in a specified thread.

Application scenario:

  1. Threads are scoped and different threads have different copies of data.
    • The scope of a Looper is a thread. Different threads have different loopers.
      • If ThreadLocal is not used, a global HashMap should be used to implement LooperManager class
  2. Object transfer under complex logic. For example, the transmission of listeners.
    • The listener can run through the execution process of the whole thread. The listener can be obtained through get() within the thread.
      • If ThreadLocal is not used,
        1. The listener is passed in the function call stack in the form of parameters( Function call stack too deep ×)
        2. The listener is accessed by the thread as a static variable( 10 threads have 10 static variables. Using ThreadLocal, each listener object is stored in its own thread)
class ThreadLocalTest {

    private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {new ThreadLocalTest().test();}

    private void test(){
        booleanThreadLocal.set(true);
        System.out.println("main---"+booleanThreadLocal.get());

        new Thread("thread1--"){
            @Override
            public void run() {
                booleanThreadLocal.set(false);
                System.out.println("thread1--"+booleanThreadLocal.get());
            }
        }.start();

        new Thread("thread2--"){
            @Override
            public void run() {
                System.out.println("thread2--"+booleanThreadLocal.get());
            }
        }.start();
    }
}
//main---true
//thread1--false
//thread2--null

The ThreadLocal instance is actually held by the class it creates (the top should be held by the thread). The value of ThreadLocal is actually held by the thread instance.

They are all on the heap, but the visibility is changed to thread visibility through some techniques.

 public void set(T value) {
    Thread t = Thread.currentThread();//1. Get the current thread object first
        ThreadLocalMap map = getMap(t);//2. Get the ThreadLocalMap of the thread object
        if (map != null)
            map.set(this, value);//If the map is not empty, perform the set operation, with the current threadLocal object as the key and the actual storage object as value
        else
            createMap(t, value);//If the map is empty, a ThreadLocalMap is created for the thread
}
    //Member variables in Thread
    ThreadLocal.ThreadLocalMap threadLocals = null;		//Each Thread thread encapsulates a ThreadLocalMap object
		
    //Get the ThreadLocalMap object in the Thread class from the ThreadLocal class
    ThreadLocalMap getMap(Thread t) {
		return t.threadLocals;
    }
		
    //Create a ThreadLocalMap member object in the Thread Local class
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

Each Thread object has a ThreadLocalMap object. This design has two purposes:

  1. It can ensure that relevant objects can be recycled immediately when the current thread ends;
  2. ThreadLocalMap elements will be greatly reduced, because too large a Map is easy to cause hash conflicts and performance degradation.
 public T get() {
     Thread t = Thread.currentThread();//1. Get the current thread first
         ThreadLocalMap map = getMap(t);//2. Get the map object of the thread
         if (map != null) {//3. If the map is not empty, obtain the corresponding Entry with the threadlocal instance as the key, and then take the object from the Entry.
             ThreadLocalMap.Entry e = map.getEntry(this);
             if (e != null)
                 return (T)e.value;
         }
         return setInitialValue();//If the map is empty, that is, the first time you do not call set to get directly (or you call set and then call remove), set the initial value for it
}

In this way, in fact, each thread saves a copy of data, and Looper saves it in its current thread. This is why sThreadLocal in Looper is static, but it can get loopers of its own thread in different threads.

2. Working principle of messagequeue

MessageQueue is just a message storage unit and cannot process messages. Looper will query whether there is a new message in the form of infinite loop. If there is, it will process it and if not, it will wait.

It maintains the message list through a single linked list, which has advantages in insertion and deletion.

MessageQueue mainly contains two operations:

  • enqueueMessage(): insert a message into the message queue (single linked list insertion)
  • next(): take a message from the message queue and remove it
    • Infinite loop. If there is no message in the message queue, it will always be blocked here. When a new message arrives, next() returns the message and removes it from the single linked list.

3. Working principle of looper

3.1 use of looper

Looper is required for the work of Handler, and no error will be reported.

Looper.prepare(): creates a looper for the current thread

Then start the message loop through Looper.loop().

new Thread(){
    @Override
    public void run() {
       Looper.prepare();
       Handler h = new Handler();
       Looper.loop();
    }
}.start();

prepareMainLooper() creates a Looper for ActivityThread.

Looper. Myloop() gets the looper bound by the current thread. If it does not return null.

Looper.getMainLooper() returns the looper of the main thread, so that you can easily communicate with the main thread.

Looper exit:

Looper.myLooper().quit();//Exit Looper directly
Looper.myLooper().quitSafely();//Set an exit flag to safely exit after all the existing messages in the message queue are processed

After Looper exits, the message sent through the Handler will fail, and the Handler's send() will return false.

In the sub thread, if the Looper is created manually, exit the Looper to terminate the message loop after all things are completed, otherwise the sub thread will always be in a waiting state. After exiting Looper, this thread will terminate immediately. It is recommended to call the above two methods to exit Looper when it is not needed.

3.2 Looper underlying principle

//The scope of a Looper is a thread. Different threads have different loopers.
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

//Construction method
private Looper(boolean quitAllowed) {
   mQueue = new MessageQueue(quitAllowed);//Create a message queue
   mThread = Thread.currentThread();//Save the object of the current thread
}


public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}

Looper.prepare(): creates a looper for the current thread

public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
}

Loop. Loop() to start the message loop.

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;
        final MessageQueue queue = me.mQueue;
     	//...
        for (;;) {//Dead cycle
            Message msg = queue.next(); // might block
            if (msg == null) {//When msg is empty, it will jump out of the loop
                return;
            }
            //...
        }
}

loop() is an endless loop. The only way out of the loop is that MessageQueue's next() returns null( When loop's quit() is called, an exit status will be marked, and the next of MessageQueue will return null)

MessageQueue's next() is also an endless loop. When there is no new message, it will be blocked there. Therefore, loop() of Looper will also be blocked, so the dead loop will continue all the time. After completion, you must exit Looper to terminate the message loop.

When the next message of the MessageQueue returns a new message, the Looper will process the message: msg.target.dispatchMessage(msg) (where msg.target is the Handler object that sends the message), so that the message will be handed over to its dispatchMessage() for processing (in the Handler's line program), and finally returned to the Handler's handleMessage(Message msg) for processing.

4. Working principle of handler

Handler is the upper interface of Android message mechanism. You only need to interact with handler in the development process.

4.1 simple application of handler

The UI cannot be updated in a thread not created by it - > that is, the UI cannot be updated in a child thread.

  1. post()
public class MainActivity extends Activity{
    Handler mHandler = new Handler(Looper.getMainLooper());
    
    private void dosomething(){
        new Thread(){
          	@Override
            public void run(){
                //Time consuming operation to get results
                
                //Pass the result to the main thread through the Handler and update the UI
                mHandler.post(new Runnable(){
                   	@Override
                    public void run(){
                        //Update UI
                    }
                });
            }
        }.start();
    }
}
  1. sendMessage()
public class MainActivity extends Activity{
    Handler mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
          	//Process information according to msg.what
                case 1:
                    System.out.println("handleMessage thread id " + Thread.currentThread().getId());
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    break;                  
            }
        }       
    };
    
    private void dosomething(){
        new Thread(){
          	@Override
            public void run(){
                //Time consuming operation to get results         
                
                Message msg = new Message();
                
                msg.what = 1;//Different messages are generated so that we can make different processing operations
                msg.arg1 = 123;//Simple data can be passed in to Message through arg1 and arg2
                msg.arg2 = 321;
                
                //Pass the result to the main thread through the Handler and update the UI
                Handler.sendMessage(msg);
            }
        }.start();
    }
}

4.2 Handler principle

In the above example, a Runnable is passed to the UI thread through the Handler. In fact, Runnable will be wrapped in a Message object and then delivered to the Message queue of the UI thread.

public final boolean post(Runnable r){
	return sendMessageDelayed(getPostMessage(r),0);//Then pass this object to sendMessageDelayed()
}

private static Message getPostMessage(Runnable r){
    Message m = Message.obtain();//obtain() will get the Message object from the recycled object pool (recycling means that the Message object will be recycled to the Message pool when it is not in use). If not, create a new one
    m.callback = r;//Wrap Runnable in a Message object
    return m;
}

sendMessageDelayed() finally called sendMessageAtTime():

  1. Get the message queue where the current Handler is located (the Handler gets the message queue from the held Looper)
  2. Judge whether the message queue is empty. If it is not empty, the message will be added to the message queue. If it is empty, an exception is reported.

The process of the Handler sending a message is just like inserting a message into the message queue. The next() of MessageQueue will return a new message to Looper, and Looper will process the new message after receiving it (give it to Handler for processing), and finally call the dispatcher message (message MSG) of Handler

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {//The post: Runnable used is mCallback. handleMessage called run() of Runnable
                    return;
                }
            }
            handleMessage(msg);//Otherwise, sendMessage() is called
        }
    }

5 message loop of main thread

The main thread, that is, the UI thread, is the ActivityThread. When the ActivityThread is created, the Looper will be initialized, which is why the main thread can use the Handler by default.

The entry of the Android Application is actually ActivityThread.main(): first, the Application and the default Activity will be created and associated. The message loop of the UI thread of the Application is also created here.

public ststic void main(String[] args){
    //...
    Process.setArgV0("pre-initialized");
    //1. Create a message loop Looper, which is the message queue of the UI thread
    Looper.prepareMainLooper();
    //Start the ActivityThread, where the application will eventually start
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    AsyncTask.init();
    //2. Execute message loop
    Looper.loop();
}

After that, Looper will always get messages from the message queue and process them. The user or system continuously adds messages to the message queue through the Handler. These messages are continuously taken out, processed and recycled, making the application run quickly.

After the message loop of the main thread starts, the ActivityThread also needs a Handler to interact with the message queue. This is ActivityThread.H, which internally defines a group of message types, mainly including the start and stop processes of the four components.

private class H extends Handler { 
//Start Activity 
public static final int LAUNCH_ACTIVITY = 100; 
//Pause Activity 
public static final int PAUSE_ACTIVITY = 101; 
public static final int PAUSE_ACTIVITY_FINISHING= 102; 
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
//...
}

ActivityThread communicates between processes through ApplicationThread and AMS. AMS will call back the Binder method in ApplicationThread after completing the request of ActivityThread by means of interprocess communication, and then ApplicationThread will send a message to H. after receiving the message, h will switch to the main process (ActivityThread) for execution. This process is the message loop model of the main thread.

6 AsyncTask

6.1 threads and thread pools

Threads are mainly divided into main threads (UI threads) and sub threads (working threads). The main thread mainly deals with things related to the interface, and the sub thread mainly deals with some time-consuming operations.

In addition to Thread, it plays the role of Thread in Android: AsyncTask and IntentService. HandleThread is also a special Thread.

  • The bottom layer of AsyncTask uses thread pool
    • Encapsulates the thread pool and Handler
    • Function: to facilitate developers to update UI in sub threads
  • IntentService, the underlying layer of HandleThread directly uses threads
    • IntentService: is a service. Internally, HandleThread is used to execute the task, and exit after the task is completed.
    • HandleThread: a thread with a message loop. Its internal implementation is: Handle + Thread + Loop

6.2 use of asynctask

AsyncTask is a lightweight asynchronous task class that can execute background tasks in the thread pool, transfer the execution progress and final results to the main thread and update the UI( (abandoned by api30, directly use thread pool + Handler or use Kotlin's coprocessor)

Note: it is only suitable for background tasks that are not too time-consuming. For tasks that are particularly time-consuming, it is still handed over to the thread pool for processing.

AsyncTask is essentially a Handler+Thread encapsulation.

public abstract class AsyncTask<Params, Progress, Result>//Three generic parameters
//Params: parameter type
//Progress: type of execution progress, generally Integer
//Result: type of returned result

4 core methods:

protected void onPreExecute() {}
protected abstract Result doInBackground(Params... params);
protected void onProgressUpdate(Progress... values) {}
protected void onPostExecute(Result result) {}
  1. onPreExecute(): it is executed in the main thread and before the asynchronous task is executed. Some preparations are made.
  2. doInBackground(Params... params): thread pool execution, used to execute asynchronous tasks.
  3. onProgressUpdate(Progress... values): executed in the main thread. This method will be called when the execution progress of background tasks changes.
  4. onPostExecute(Result result): after the main thread is executed, after the asynchronous task is completed, it is called.

use:

class DownloadFilesTask extends AsyncTask<URL,Integer,Long> {
    @Override
    protected Long doInBackground(URL... urls) {

        int cnt = urls.length;
        long totalSize = 0;
        for(int i=0;i<cnt;i++){
            totalSize += DownLoader.downloadFile(urls[i]);
            //When the progress changes, call publishprogress (process)
            publishProgress((int) ((i/(float)cnt)*100));
            if(isCancelled()){
                break;
            }
        }

        return totalSize;
    }

    @Override
    protected void onProgressUpdate(Integer... process) {
        setProgressPercent(process[0]);
    }

    @Override
    protected void onPostExecute(Long result) {
        showDialog("Download"+result+" bytes");
    }
}

Called on the main thread

new DownloadFilesTask.execute(url1,url2,url3);

6.3 working principle of asynctask

The most suitable scenario for AsyncTask is multithreading. It has been seen in the code that there is a thread pool maintained within AsyncTask. The default is SERIAL_EXECUTOR

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

Why is AsyncTask required to load on the main thread (when AsyncTask is used to update the UI)?

When the AsyncTask constructor is executed, the internal handler will be initialized and the

  • The incoming Looper is empty or the incoming Looper is the Looper of the main thread: get the static Handler (the Handler in the main thread)
  • Otherwise: create a Handler for the child thread of the incoming Looper
    public AsyncTask(@Nullable Looper callbackLooper) {
		mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() //Ask AsyncTask to load on the main thread
            ? getMainHandler()
            : new Handler(callbackLooper);
    }

	//mHandler initialization
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }
	
	//sHandler
	private static InternalHandler sHandler;

    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:
                    //Receive the end of the task: call finish() to return the result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //Update progress received: call onProgressUpdate() to update progress
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

InternalHandler is used to switch the execution environment from the thread pool to the main thread.

//Construction method

public AsyncTask() {
    this((Looper) null);
}

public AsyncTask(@Nullable Handler handler) {
    this(handler != null ? handler.getLooper() : null);
}

public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() //Ask AsyncTask to load on the main thread
            ? 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);//Perform time-consuming operations
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);//The result is passed to the internal Handler (jump to the main thread and return the 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);
                }
            }
        };
}

	//postResult: the result is passed to the internal Handler (jump to the main thread and return the result)
    private Result postResult(Result result) {
        // getHandler() returns an InternalHandler instance, a main thread Handler
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();//Handler sends a message
        return result;
    }

	//obtainMessage((what)MESSAGE_POST_RESULT,(Object)new AsyncTaskResult<Result>(this, result))
    public final Message obtainMessage(int what, @Nullable Object obj) {
        return Message.obtain(this, what, obj);
    }
	
	//AsyncTaskResult
    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

Initialize two objects: mWorker and mFuture. mWorker is a Callable object that serves as a build parameter for mFuture. mFuture is a FutureTask object, and mWorker is passed in as a parameter when initializing mFuture.

public class FutureTask<V> implements RunnableFuture<V> {
    //...
	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 {
                    //Then we call the execute() method of the thread pool (the run method to execute mFuture).
                    //Perform specific time-consuming tasks, that is, the contents of the call() method in mWorker in the beginning constructor.
                    //After the doInBackground() method is executed, the postResult() method is executed
                    result = c.call();

                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            
            runner = null;
            
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    //...
}

execute():

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
     return executeOnExecutor(sDefaultExecutor, params);
}

    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);//Load mFuture into the serial thread pool

        return this;
    }

exec, that is, sdefaultexecution, is a serial thread pool in which all asynctasks in a process are queued for execution. Specifically, the SerialExecutor class

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();//1. Add a new task, mFuture
                    } finally {
                        scheduleNext();//Call THREAD_POOL_EXECUTOR performs tasks at the head of the queue
                    }
                }
            });
            //Judge whether there is an ongoing task. If not, execute the next task
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            // Get a task from the task queue and put it into the thread pool for execution
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

A queue is maintained inside the SerialExecutor. The lock ensures that the tasks in AsyncTask are executed serially, that is, multiple tasks need to be added to the queue one by one, and then execute the next after executing the queue header, and so on.

AsyncTask maintains two thread pools (SerialExecutor, thread)_ POOL_ EXECUTOR):

  • SerialExecutor: used to queue tasks
  • THREAD_POOL_EXECUTOR: used to actually perform tasks

THREAD_POOL_EXECUTOR: actually, it is a thread pool, which enables a certain number of core threads and working threads.

    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), sThreadFactory);
        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

Then we call the execute() method of the thread pool. Perform specific time-consuming tasks, that is, the contents of the call() method in mWorker in the beginning constructor. First execute the doInBackground() method, and then execute the postResult() method. See the details of this method below:

//postResult: the result is passed to the internal Handler (jump to the main thread and return the result)
private Result postResult(Result result) {
        // getHandler() returns an InternalHandler instance, a main thread Handler
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

This method sends a message to the Handler object. See the source code of the instantiated Hanlder object in AsyncTask for details:

private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // Call finish()
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //Update progress
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

In InternalHandler, if the received message is MESSAGE_POST_RESULT, that is, when the doInBackground() method is executed and the result is passed, the finish() method is called.

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

If the task has been canceled, call back the onCancelled() method; otherwise, call back the onPostExecute() method.

AsyncTask's default thread pool (SerialExecutor) is serial. If you want to execute tasks in parallel, you can use executeOnExecutor() to pass in a custom thread pool to execute tasks.

You can pass in your own customized thread pool in executeOnExecutor:

//Execute in the same order as the default
asyncTask.executeOnExecutor(Executors.newSingleThreadExecutor());
//Unlimited Executor
asyncTask.executeOnExecutor(Executors.newCachedThreadPool());
//Execute 10 executors simultaneously
asyncTask.executeOnExecutor(Executors.newFixedThreadPool(10));

Other properties of AsyncTask:

//Gets the number of CPU s
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//Core worker threads (number of threads executing simultaneously)
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//The maximum number of threads allowed in the thread pool
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//Idle thread timeout in seconds
private static final int KEEP_ALIVE = 1;
//Thread factory
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());
    }
};
//Blocking queue, used to save tasks to be executed (up to 128)
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

6.4 consequences of improper use of asynctask

  1. life cycle

AsyncTask does not bind to any component life cycle, so when creating and executing AsyncTask in Activity/Fragment, it is best to call cancel(boolean) in onDestory() of Activity/Fragment;

  1. Memory leak

If AsyncTask is declared as a non static internal class of an Activity, AsyncTask will retain a reference to the Activity that created the AsyncTask. If the Activity has been destroyed and the background thread of AsyncTask is still executing, it will continue to keep this reference in memory, so that the Activity cannot be recycled, resulting in memory leakage.

  1. Result loss

The screen rotation or the system killing the Activity in the background will lead to the re creation of the Activity. The previously running AsyncTask (non static internal class) will hold a reference to the previous Activity, which is no longer valid. In this case, calling onPostExecute() to update the interface will no longer take effect.

PU_COUNT * 2 + 1;
//Idle thread timeout in seconds
private static final int KEEP_ALIVE = 1;
//Thread factory
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());
}

};
//Blocking queue, used to save tasks to be executed (up to 128)
private static final BlockingQueue sPoolWorkQueue =
new LinkedBlockingQueue(128);



### 6.4 consequences of improper use of asynctask

1. life cycle

AsyncTask Does not bind to any component lifecycle, so in Activity/Fragment Create execution in AsyncTask When, it's best to Activity/Fragment of onDestory()call cancel(boolean);

2. Memory leak

If AsyncTask Declared as Activity Non static inner class, then AsyncTask Will keep a pair created AsyncTask of Activity Reference to. If Activity Has been destroyed, AsyncTask The background thread of is still executing. It will continue to keep this reference in memory, resulting in Activity Cannot be recycled, causing a memory leak.

3. Result loss

Screen rotation or Activity Being killed by the system in the background will lead to Activity Re creation of, previously run AsyncTask(Non static inner classes) will hold a previous Activity This reference is invalid. At this time, call onPostExecute()Updating the interface will no longer take effect.




Tags: Java Android message queue

Posted on Wed, 01 Sep 2021 19:54:18 -0400 by ZaZall