The hidden trap of asynctask first looks at an instance.
This example is very simple. It is strange to show an extreme usage of asynctask.
public class AsyncTaskTrapActivity extends Activity { private SimpleAsyncTask asynctask; private Looper myLooper; private TextView status; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); asynctask = null; new Thread(new Runnable() { @Override public void run() { Looper.prepare(); myLooper = Looper.myLooper(); status = new TextView(getApplication()); asynctask = new SimpleAsyncTask(status); Looper.loop(); } }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); setContentView((TextView) status, params); asynctask.execute(); } @Override public void onDestroy() { super.onDestroy(); myLooper.quit(); } private class SimpleAsyncTask extends AsyncTask<Void, Integer, Void> { private TextView mStatusPanel; public SimpleAsyncTask(TextView text) { mStatusPanel = text; } @Override protected Void doInBackground(Void... params) { int prog = 1; while (prog < 101) { SystemClock.sleep(1000); publishProgress(prog); prog++; } return null; } // Not Okay, will crash, said it cannot touch TextView @Override protected void onPostExecute(Void result) { mStatusPanel.setText("Welcome back."); } // Okay, because it is called in #execute() which is called in Main thread, so it runs in Main Thread. @Override protected void onPreExecute() { mStatusPanel.setText("Before we go, let me tell you something buried in my heart for years..."); } // Not okay, will crash, said it cannot touch TextView @Override protected void onProgressUpdate(Integer... values) { mStatusPanel.setText("On our way..." + values[0].toString()); } }}
This example cannot run normally in android2.3. An exception is reported when onprogressupdate () and onpostexecute () are executed.
11-03 09:13:10.501: E/AndroidRuntime(762): FATAL EXCEPTION: Thread-1011-03 09:13:10.501: E/AndroidRuntime(762): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.11-03 09:13:10.501: E/AndroidRuntime(762): at android.view.ViewRoot.checkThread(ViewRoot.java:2990)11-03 09:13:10.501: E/AndroidRuntime(762): at android.view.ViewRoot.requestLayout(ViewRoot.java:670)11-03 09:13:10.501: E/AndroidRuntime(762): at android.view.View.requestLayout(View.java:8316)11-03 09:13:10.501: E/AndroidRuntime(762): at android.view.View.requestLayout(View.java:8316)11-03 09:13:10.501: E/AndroidRuntime(762): at android.view.View.requestLayout(View.java:8316)11-03 09:13:10.501: E/AndroidRuntime(762): at android.view.View.requestLayout(View.java:8316)11-03 09:13:10.501: E/AndroidRuntime(762): at android.widget.TextView.checkForRelayout(TextView.java:6477)11-03 09:13:10.501: E/AndroidRuntime(762): at android.widget.TextView.setText(TextView.java:3220)11-03 09:13:10.501: E/AndroidRuntime(762): at android.widget.TextView.setText(TextView.java:3085)11-03 09:13:10.501: E/AndroidRuntime(762): at android.widget.TextView.setText(TextView.java:3060)11-03 09:13:10.501: E/AndroidRuntime(762): at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$SimpleAsyncTask.onProgressUpdate(AsyncTaskTrapActivity.java:110)11-03 09:13:10.501: E/AndroidRuntime(762): at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$SimpleAsyncTask.onProgressUpdate(AsyncTaskTrapActivity.java:1)11-03 09:13:10.501: E/AndroidRuntime(762): at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:466)11-03 09:13:10.501: E/AndroidRuntime(762): at android.os.Handler.dispatchMessage(Handler.java:130)11-03 09:13:10.501: E/AndroidRuntime(762): at android.os.Looper.loop(Looper.java:351)11-03 09:13:10.501: E/AndroidRuntime(762): at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$1.run(AsyncTaskTrapActivity.java:56)11-03 09:13:10.501: E/AndroidRuntime(762): at java.lang.Thread.run(Thread.java:1050)11-03 09:13:32.823: E/dalvikvm(762): [DVM] mmap return base = 4585e000
However, it runs normally in android4.0 and later versions (Version 3.0 is not tested ).
From the stacktrace of 2.3 runtime, the reason is that the UI component is operated in a non-UI thread. No, it's amazing. The document of asynctask # onprogressupdate () and asynctask # onpostexecute () clearly states that these two Callbacks are in the UI thread, how can this exception be reported!
Cause Analysis
Asynctask is designed to execute asynchronous tasks but can communicate with the main thread. Its internal internalhandler is a static member shandler inherited from the handler, which is used to communicate with the main thread. Let's take a look at the Declaration of this object: Private Static final internalhandler shandler = new internalhandler (); While internalhandler inherits from handler. In essence, shandler is a handler object. Handler is used for communication with threads. It must be used together with logoff and thread binding. When creating a handler, you must specify logoff. If no logoff object is specified, the thread where the call stack is located is used, if the call stack thread does not have logoff, an exception is reported. It seems that this shandler is used to call new
The thread bound to internalhandler () is static and private, that is, it is bound to the thread that created the asynctask object for the first time. Therefore, if the asynctask object is created in the main thread, its shandler is bound to the main thread, which is normal. In this example, asynctask is created in the derivative thread, so its shandler is bound to the derivative thread. Therefore, it naturally cannot operate on the UI element and will be in onprogressupdate () and onpostexecute ().
The exception in the preceding example is that the simpleasynctask object is created in the derivative thread. As to why there is no problem in version 4.0, it is because activitythread in version 4.0. in the main () method, the bindapplication action will be performed. In this case, the asynctask object will be used and the shandler object will be created. This is the main thread, so the shandler is bound to the main thread. When the asynctask object is created later, the shandler will not be initialized again because it has been initialized. As for what is bindapplication, why does the bindapplication action not affect the discussion of this issue.
Asynctask defects and modification methods
This is actually a hidden bug of asynctask. It should not be so dependent on developers and should impose conditions to ensure that the first asynctask object is created in the main thread:
1. Check whether the current thread is the main thread in the Construction of internalhandler, and then throw an exception. Obviously this is not the best practice.
new InternalHandler() {
if (Looper.myLooper() != Looper.getMainLooper()) { throw new RuntimeException("AsyncTask must be initialized in main thread"); }
11-03 08:56:07.055: E/AndroidRuntime(890): FATAL EXCEPTION: Thread-1011-03 08:56:07.055: E/AndroidRuntime(890): java.lang.ExceptionInInitializerError11-03 08:56:07.055: E/AndroidRuntime(890): at com.hilton.effectiveandroid.os.AsyncTaskTrapActivity$1.run(AsyncTaskTrapActivity.java:55)11-03 08:56:07.055: E/AndroidRuntime(890): at java.lang.Thread.run(Thread.java:1050)11-03 08:56:07.055: E/AndroidRuntime(890): Caused by: java.lang.RuntimeException: AsyncTask must be initialized in main thread11-03 08:56:07.055: E/AndroidRuntime(890): at android.os.AsyncTask$InternalHandler.<init>(AsyncTask.java:455)11-03 08:56:07.055: E/AndroidRuntime(890): at android.os.AsyncTask.<clinit>(AsyncTask.java:183)11-03 08:56:07.055: E/AndroidRuntime(890): ... 2 more
2. It is better to pass the mainlogoff of the main thread to the internalhandler during the construction.
new IntentHandler() { super(Looper.getMainLooper());}
Will someone write it like this? Normally, no one will intentionally create AsyncTask in the derivative thread. However, if there is a Worker class used to complete asynchronous tasks to download images from the network, and then it is displayed that there is also a WorkerScheduler to allocate tasks, and WorkerScheduler is also running in a separate thread, worker is implemented by AsyncTask. WorkScheduler creates a Worker to complete the request when receiving the request. In this case, the WorkerScheduler thread --- derivative thread --- creates an AsyncTask object. This Bug is extremely concealed and hard to be found.
How to restrict caller threads
Under normal circumstances, a Java application process has a thread, and the entry is the main method. Android applications are essentially Java applications, and their main entry is in ActivityThread. main (), The logoff will be called in the main () method. prepareMainLooper (), which initializes the Looper of the main thread and stores the Looper object mMainLooper of the main thread in the Looper. It also provides methods to obtain the Looper and getMainLooper () of the main thread (). Therefore, if you need to create a Handler bound to the main thread, you can use new
Handler (loler. getmainloler () to ensure that it is indeed bound to the main thread.
If you want to ensure that some methods can only be called in the main thread, you can check the logoff object of the caller:
if (Looper.myLooper() != Looper.getMainLooper()) { throw new RuntimeException("This method can only be called in main thread");}
Handler, logoff, messagequeue mechanism interaction and collaboration between threads
Although the thread and thread share the memory space, that is, they can access the heap space of the process, but the thread has its own stack, method calls running in a thread are all in the thread's own call stack. Generally speaking, the West thread is a run () method and the method called internally. All method calls here are independent of other threads. Because of the relationship between method calls, another method is called by one method, and another method also occurs in the thread of the caller. Therefore, a thread is a time series concept, essentially a column of method calls.
To collaborate between threads, or to change the thread where a method is located (in order not to block its own thread), the thread can only send a message to another thread, and then return; in addition, the thread executes some operations after receiving the message. If a simple operation can be identified by a variable, for example, when thread a needs thread B to do something, a certain object OBJ can be set. Then, when thread B sees OBJ! = NULL. This type of thread interaction and collaboration has many examples in Java programming ideas.
ITC-Inter thread communication in Android
Note: Of course, handler can also be used as a message loop within a thread, without having to communicate with other threads. But here we will focus on the tasks between threads.
Android imposes a special restriction that the non-main thread cannot operate the UI elements, and an application cannot create derivative threads, in this way, the main thread and the derivative thread must communicate. Because of this frequent communication, it is impossible to identify all variables, and the program will become very messy. At this time, the Message Queue becomes very necessary, that is, to create a message queue in each thread. When a requires B, A sends a message to B. In essence, a adds the message to B's message queue. A returns this message, and B does not wait for a message, instead, you can view the Message Queue cyclically and execute the message after seeing the message.
The basic idea of the entire ITC is to define a message object, put the required data into it, and define the message processing method as Callback and put it into the message, then, the message is sent to another thread. The other thread processes the messages in Its Queue cyclically. When the message is displayed, the callback attached to the message is called to process the message. As a result, it can be seen that this only changes the execution sequence of the Message Processing: normally, it is processed on the spot, which is encapsulated into a message and thrown to another thread and executed at an uncertain time; in addition, the thread only provides the CPU time sequence, and does not interfere with what the message is and how the message is processed. In short, a method is put into another thread for calling, and then the call stack (call
Stack), the call stack of this method is transferred to another thread.
So what is the change in this mechanism? From the above, we can see that it only arranges one method (Message Processing) to another thread for (asynchronous processing), instead of doing it immediately, it changes the execution sequence of the CPU ).
So where is the message queue stored? It cannot be placed in the heap space (New messagequeue (). In this way, the object reference is easy to lose and it is not easy to maintain for the thread. JAVA supports threadlocal local storage of threads. Through threadlocal objects, you can place objects in the thread space. Each thread has its own object. Therefore, you can create a message queue for each thread to communicate with and store it in its local storage.
This model can also be extended, such as defining priority for messages.
Messagequeue
Messages are stored in a queue. Two operations are performed: enqueuemessage and next (). thread security is required, because columns are usually called by another thread.
Messagequeue is a mechanism very close to the underlying layer, so it is not convenient for developers to use it directly. To use this messagequeue, there must be two aspects of work. One is the target thread end: creation, associated with the thread, the other is the client of the queue thread: Creates a message, defines callback processing, and sends a message to the queue. Logoff and handler are encapsulation of messagequeue: logoff is used for the target thread. The purpose is to create messagequeue, associate messagequeue with the thread, and run messagequeue, logoff has a protection mechanism to allow a thread to create only one messagequeue object, while handler is used for the queue client to create a message, define the callback and send a message.
Because the logoff object encapsulates the target queue thread and Its Queue, for the client of the queue thread, The logoff object represents a thread with messagequeue and the messagequeue of the thread. That is to say, when you build a handler object, you use a logoff object. When you test whether a thread is the expected thread, you also use a logoff object.
Loggin
The logoff task is to create a message queue messagequeue, put it in threadlocal of the thread (associated with the thread), and run the messagequeue in ready state, the interface must be provided to stop the message loop. It has four main interfaces:
- Public static void lorule. Prepare ()
This method creates a loose object and messagequeue object for the thread, and puts the loose object into the thread space through threadlocal. Note that this method can only be called once by each thread. The common practice is in the first sentence of the thread run () method, but you only need to ensure that it is before loop.
- Public static void logoff. Loop ()
This method must be called after prepare () to run the messagequeue of the thread. Once this method is called, the thread will loop infinitely (while (true) {...}), sleep when there is no message, and wake up when there is a message in the queue until quit () is called. Its Simplified Implementation is:
loop() { while (true) { Message msg = mQueue.next(); if msg is a quit message, then return; msg.processMessage(msg) }}
- Public void loid. Quit ()
Let the thread end the messagequeue loop and terminate the loop. The run () method ends and the thread stops. Therefore, it is an object method, meaning to terminate a loose object. You must remember to call this method when you do not need a thread. Otherwise, the thread will not terminate and exit, and the process will continue to run, occupying resources. If a large number of threads do not exit, the process will eventually collapse.
- Public static logoff lorule. mylogoff ()
This is the method to obtain the logoff object owned by the caller's thread.
There are two other interfaces related to the main thread:
- One is specially prepared for the main thread
Public static void loid. preparemainlooper ();
This method is only used to initialize logoff for the main thread. It is only used in activitythread. the main () method cannot be called elsewhere or by other threads. if an exception is thrown when called in the main thread, a thread can only create one logoff object. However, if this method is called in other threads, The mainlooper will be changed. The next getmainlooper will return it instead of the looper object of the real main thread. This will not throw an exception, there will be no obvious errors, but the program will not work normally, because the method originally designed to run in the main thread will be transferred to this thread, it will produce a very strange bug. Here, the lorule. preparemainthread () method should include the following judgment:
public void prepareMainLooper() { if (getMainLooper() != null) { throw new RuntimeException("Looper.prepareMainthread() can ONLY be called by Frameworks"); } //...}
To prevent unauthorized calls by other threads, it is far from sufficient document constraints.
- The other interface is to get the Logoff of the main thread:
Public static Looper. getmainlooper ()
This is mainly used to check the thread validity, that is, to ensure that some methods can only be called in the main thread. But this is not safe. As mentioned above, if a derivative thread calls preparemainlooper (), it will change the real mmainlooper. This derivative thread can pass the above detection, resulting in getmainlooper ()! = Mylogoff () detection becomes unreliable. So the viewroot method is to use thread to detect:mThread
!= Thread.currentThread();
Its mthread is used when the system creates viewroot. obtained by currentthread (). This method is more reliable to check whether it is the main thread, because it does not depend on the external, but trust the reference of the thread saved by itself.
Message object
A message is only a data structure and a carrier of information. It has nothing to do with the queue mechanism and encapsulates the necessary information of the actions to be executed and the actions to be executed,What, arg1, arg2, OBJIt can be used to transmit data.MessageThe callback must passHandlerTo define, why? Because message is only a carrier, it cannot run to the target by itselfMessagequeueAbove, it must beHandlerTo operateMessagePut it in the target queue, since it requires handler to put it in a unified mannerMessagequeue, You can also makeHandlerTo define the callback for processing messages. Note that the sameMessageThe object can only be used once, because the message will be recycled after processing, soMessageThe object can only be used once.MessagequeueAn exception is thrown.
Handler object
It is designed to facilitate operations on the queue thread client and hide direct operations.MessagequeueComplexity. Handler is mainly used to send messagesHandlerBound threadMessagequeueTherefore, you must specifyLogoffObject.LogoffObtainLogoffObject. It has many heavy-load send * message and post methods, which can be used to send messages to the target queue in multiple ways, send messages at the sending time, or put them in the queue's header;
It also has two functions: one is to create a message object through the obtain * system method, and the other is to define the ProcessingMessageCallbackMcallbackAndHandlemessageBecause a handler may send more than one message, these messages usually shareHandlerSo inHandlemessageOrMcallbackIt is necessary to distinguish these different messages, usually based on message. What, of course, you can also use other fields, as long as you can distinguish different messages. It must be noted that the messages in the message queue are independent and irrelevant. The message namespace is in the handler object, becauseMessageYesbyHandlerOnly oneHandlerDifferent message objects must be differentiated. Broadly speaking, if a message has a processing method defined by itself, all messages are irrelevant. When a message is retrieved from the queue, the callback Method on it is called, there will be no naming conflicts,HandlerThe callback Processing Method for sent messages isHandler. handlemessageOrHandler. mcallback, So there will be an impact, but the scope of the impact is also limited to the sameHandlerObject.
BecauseHandlerThe function is to send messages to the target queue and define the callback for processing messages (processing messages). It only depends on the thread.Messagequeue, SoHandlerYou can bind any number to a specificMessagequeue. WhileMessagequeueThere is a limit on the number of threads, each thread can have only one,MessagequeuePassLogoffCreate,LogoffStorageThreadlocalMedium,LogoffOnly one thread can be created. HoweverHandlerNo such restriction,HandlerThrough its constructor, you only need to provideLogoffSo there is no limit on the number of objects.