Android:异步处理之Handler+Thread的应用(一)
前言
很久很久以前就聽說了,每一個android的應用程序都會分別運行在一個獨立的dalvik虛擬機進程中,而在每個虛擬機在啟動時會運行一個UI主線程(Main Thread),而為啥叫UI主線程而不是AI主線程或者是BI主線程呢?因為它要處理全部和UI相關的事件;因為Android系統采用的是UI單線程模型,只能由UI主線程對其進行UI操作,如果子線程抱著眾人拾柴火焰高的覺悟來幫忙UI主線程更新UI界面的話,對不起哦~Android系統就會報錯的。粗俗點講就是:我們只能通過UI主線程來蹂躪UI界面,但是其他線程來的話會被告弓雖女干滴。。
那么現在問題來了!鑒于近來挖掘機那么火,我也不好意思繼續問這個問題了。。。嗯嗯~網絡操作之類耗時操作就像挖掘機那樣,我們在下載文件的時候一樣跟挖掘機挖個大坑一樣需要一定的時間;當挖掘機司機挖好一個大坑要找老板反饋工作完成一樣,我們下載好一個文件自然要馬上告訴屏幕前苦逼等待的用戶們,誰知道他們多著急想看**.avi呢;但是你在挖坑時好意思叫老板在旁邊看你嗎?老板分分鐘為幾千萬上下的事忙著呢~所以嘛同理,對于網絡操作,我們當然也不能在UI主線程中進行網絡操作,因為這樣會阻塞主線程造成界面卡死,也會造成ANR(應用程序無響應)。我們應該把文件下載、文件讀取諸如此類的耗時操作放到子線程中去進行,等到子線程耗時操作完成時通知UI界面做出響應。
不要在UI主線程中進行耗時操作
如果你不信邪一定要在UI主線程進行下載文件、加載大文件之類的耗時操作。如下代碼:
private Button btn; //onCreate之類的生命周期的方法就是允許在UI主線程中 @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {downLoad();//調用UI主線程的下載函數}}); }private void downLoad(){try {Thread.sleep(10000);//休眠10秒,模擬網絡文件下載耗時操作} catch (InterruptedException e) {e.printStackTrace();} }你會發現界面卡主了10秒:(模擬下載操作的按鈕為深色,說明按鈕一直為按下狀態)
如果這時候你手比較管不住的話,雖然點幾下界面,沒事~Androi系統會馬上送你一份ANR大禮哦,而且還不用998元耶!
小結一個:不要在UI主線程中進行耗時操作,你可能會疑問什么是UI主線程,UI主線程主要運行的就是Activity、Service等里面的生命周期方法,所以不要在生命周期方法如onCreate()中進行下載這些大事件。對于耗時操作,我們應該新建一個子線程并交給他處理,但是還需要注意一點。
不要在子線程中更新UI界面
既然我們說下載文件要在子線程中進行,那么我們就新建一個子線程把下載操作放到里面進行咯,代碼如下:
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);text = (TextView) findViewById(R.id.text);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {new Thread(){@Overridepublic void run() {//在子線程中進行下載操作try {Thread.sleep(10000);//休眠10秒,模擬耗時操作} catch (InterruptedException e) {e.printStackTrace();}text.setText("下載完成");//設置TextView,通知UI界面下載完成}}.start();}}); }10秒后,你覺得會在UI界面完美顯示“下載完成”么?一般,出現這個才符合Androi系統的一貫作風
并且在Log中報錯如下
小弟英語其實很廢柴,但是隱隱約約有人告訴我:這不是叫只能在主線程中更新UI嗎?不信,金山翻譯一下去呀。。。。
小結一個:不要在子線程中更新UI界面,這樣會導致android系統報錯、應用崩潰退出。UI界面時單線程模式,我們只能通過UI主線程中對UI的界面進行相關的更新,千萬不要越線辦事,你要記住的是~UI界面是UI主線程的老婆,你們這些子線程誰都別想動!
利用Thread+Handler進行異步處理
那么問題來了,現在我們需要進行耗時操作(例如下載文件)時不能在主線程執行,我們又需要在UI界面通知用戶我們活干完了不能再子線程中執行。這似乎是一個棘手的熱山芋呀,幸好谷歌給我們提供了一個救我們于危難之中的Handler,一個能讓主線程監聽子線程發送來消息的東東,至于Handler的實現原理我會在后面的文章詳細介紹,現在我們只需要先了解Handler的用法。
private Button btn; private TextView text;private Handler handler = new Handler(){private int process = 0;@Overridepublic void handleMessage(Message msg) {switch(msg.what){case 0://更細下載進度process += 1;text.setText("下載" + process + "%");//在主線程中更新UI界面break;case 1://提示下載完成text.setText("下載完成");//在主線程中更新UI界面break;default:break;}} }; //onCreate之類的生命周期的方法就是允許在UI主線程中 @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn = (Button) findViewById(R.id.btn);text = (TextView) findViewById(R.id.text);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {new Thread(){@Overridepublic void run() {//在子線程中進行下載操作for(int i = 0; i < 100; i++){try {Thread.sleep(200);//休眠0.2秒,模擬耗時操作} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage(0);//發送消息到handler,通知下載進度}handler.sendEmptyMessage(1);//發送消失到handler,通知主線程下載完成}}.start();}}); }?這里來解釋一下Handler的使用方法:
1、我們為了不阻塞主線程,將下載任務通過子線程來執行。
new Thread(){@Overridepublic void run() {//在子線程中進行下載操作for(int i = 0; i < 100; i++){try {Thread.sleep(200);//休眠0.2秒,模擬耗時操作} catch (InterruptedException e) {e.printStackTrace();}handler.sendEmptyMessage(0);//發送消息到handler,通知下載進度}handler.sendEmptyMessage(1);//發送消失到handler,通知主線程下載完成} }.start();2、當子線程需要跟主線程交流時,也就是當子線程要跟UI主線程說:親,偶下載文件到80%了或者偶已經把文件下載完成了!執行這句代碼
handler.sendEmptyMessage(1);//發送消失到handler,通知主線程下載完成3、當發送空消息之后,在Handler將會收到子線程發來的消息,觸發回調方法handlerMessage(),我們就在這里對UI界面進行更新,這個回調方法是運行在UI主線程的
@Override public void handleMessage(Message msg) {switch(msg.what){case 0://更細下載進度process += 1;text.setText("下載" + process + "%");//在主線程中更新UI界面break;case 1://提示下載完成text.setText("下載完成");//在主線程中更新UI界面break;default:break;} }4、最后,UI界面更新成功!(圖嘛,我這里就不上了。。。。)
小結一個:對于比較耗時間的任務,我們一般需要放在子線程中執行;當子線程更新UI界面時,子線程可以通過Handler來通知主線程更新,一般通過發送消息來觸發handlerMessage()這個回調方法來執行UI界面的更新。
進一步簡略de操作:handler.post方法和view.post方法
但是如果你覺得每次都要重寫handlerMessage()比較麻煩,我們完全可以用更加簡略的方法來解決我們的需求,就是用handler中的post方法。代碼如下
new Thread(){@Overridepublic void run() {//在子線程中進行下載操作try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}handler.post(new Runnable() {@Overridepublic void run() {text.setText("下載完成");}});//發送消失到handler,通知主線程下載完成} }.start();這樣處理的話我們就可以不用重寫handlerMessage()方法了,適合子線程與主線程進行較為單一的交流。但在這里我們要強調的一點的是,post里面的Runnable還是在UI主線程中運行的,而不會另外開啟線程運行,千萬不要在Runnable的run()里面進行耗時任務,不然到時又ANR了可別找我哦。。
如果你有時候連handler都不想搞,還可以這樣寫代碼滴。
我們只需要把handler換成View組件進行post,更新任務自然會加載到UI主線程中進行處理。
text.post(new Runnable() {@Overridepublic void run() {text.setText("下載完成");} });//發送消失到handler,通知主線程下載完成至于Handler機制以及這兩種post的原理,我將會在后面的博客文章中專題介紹,這里只提供一個使用方法而已。
?
終于寫完了睡覺,怎么沒有掌聲呢??雞蛋、啤酒瓶砸幾個上來~~~~哈哈
權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/fnhfire_7030/article/details/79518819前言:又到了一年一度的跳槽季,準備跳槽的你在關于Android面試方面的知識都完全掌握了嗎?Android面試中經常被問到的知識——Android消息機制即Handler有關的問題你都能解釋的清楚嗎?如果你對Android消息機制比較模糊或者能夠回答與Handler有關的問題但是不清楚其中的原理,那么你將會在本文得到你想要的答案。
閱讀本文后的收貨??閱讀本文后你將會有以下收獲:
清楚的理解Handler的工作原理理清Handler、Message、MessageQueue以及Looper之間的關系知道Looper是怎么和當前線程進行綁定的是否能在子線程中創建Handler獲得分析Handler源碼的思路要想有以上的收獲,就需要研究Handler的源碼,從源碼中來得到答案。
開始探索之路Handler的使用??先從Handler的使用開始。我們都知道Android的主線程不能處理耗時的任務,否者會導致ANR的出現,但是界面的更新又必須要在主線程中進行,這樣,我們就必須在子線程中處理耗時的任務,然后在主線程中更新UI。但是,我們怎么知道子線程中的任務何時完成,又應該什么時候更新UI,又更新什么內容呢?為了解決這個問題,Android為我們提供了一個消息機制即Handler。下面就看下Handler的常見使用方式,代碼如下
public class MainActivity extends AppCompatActivity implements View.OnClickListener {? ? private Button mStartTask;
? ? @SuppressLint("HandlerLeak")? ? private Handler mHandler = new Handler() {? ? ? ? @Override? ? ? ? public void handleMessage(Message msg) {? ? ? ? ? ? super.handleMessage(msg);? ? ? ? ? ? if (msg.what == 1) {? ? ? ? ? ? ? ? Toast.makeText(MainActivity.this, "刷新UI、", Toast.LENGTH_SHORT).show();? ? ? ? ? ? }? ? ? ? }? ? };
? ? @Override? ? protected void onCreate(Bundle savedInstanceState) {? ? ? ? super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? initView();? ? }
? ? private void initView() {? ? ? ? mStartTask = findViewById(R.id.btn_start_task);? ? ? ? mStartTask.setOnClickListener(this);? ? }
? ? @Override? ? public void onClick(View v) {? ? ? ? switch (v.getId()) {? ? ? ? ? ? case R.id.btn_start_task:? ? ? ? ? ? ? ? new Thread(new Runnable() {? ? ? ? ? ? ? ? ? ? @Override? ? ? ? ? ? ? ? ? ? public void run() {? ? ? ? ? ? ? ? ? ? ? ? try {? ? ? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(1000);? ? ? ? ? ? ? ? ? ? ? ? ? ? mHandler.sendEmptyMessage(1);? ? ? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {? ? ? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }).start();? ? ? ? ? ? ? ? break;? ? ? ? }? ? }}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546可以看到在子線程中,讓線程睡了一秒,來模仿耗時的任務,當耗時任務處理完之后,Handler會發送一個消息,然后我們可以在Handler的handleMessage方法中得到這個消息,得到消息之后就能夠在handleMessage方法中更新UI了,因為handleMessage是在主線程中嘛。到這里就會有以下疑問了:
Handler明明是在子線程中發的消息怎么會跑到主線程中了呢?Handler的發送消息handleMessage又是怎么接收到的呢?帶著這兩個疑問,開始分析Handler的源碼。
Handler的源碼分析??先看下在我們實例化Handler的時候,Handler的構造方法中都做了那些事情,看代碼
final Looper mLooper;? ? final MessageQueue mQueue;? ? final Callback mCallback;? ? final boolean mAsynchronous;
/**? ? ?* Default constructor associates this handler with the {@link Looper} for the? ? ?* current thread.? ? ?*? ? ?* If this thread does not have a looper, this handler won't be able to receive messages? ? ?* so an exception is thrown.? ? ?*/? ? public Handler() {? ? ? ? this(null, false);? ? }
/**? ? ?* Use the {@link Looper} for the current thread with the specified callback interface? ? ?* and set whether the handler should be asynchronous.? ? ?*? ? ?* Handlers are synchronous by default unless this constructor is used to make? ? ?* one that is strictly asynchronous.? ? ?*? ? ?* Asynchronous messages represent interrupts or events that do not require global ordering? ? ?* with respect to synchronous messages.? Asynchronous messages are not subject to? ? ?* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.? ? ?*? ? ?* @param callback The callback interface in which to handle messages, or null.? ? ?* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for? ? ?* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.? ? ?*? ? ?* @hide? ? ?*/? ? public Handler(Callback callback, boolean async) {? ? ? ? if (FIND_POTENTIAL_LEAKS) {? ? ? ? ? ? final Class<? extends Handler> klass = getClass();? ? ? ? ? ? if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&? ? ? ? ? ? ? ? ? ? (klass.getModifiers() & Modifier.STATIC) == 0) {? ? ? ? ? ? ? ? Log.w(TAG, "The following Handler class should be static or leaks might occur: " +? ? ? ? ? ? ? ? ? ? klass.getCanonicalName());? ? ? ? ? ? }? ? ? ? }
? ? ? ? mLooper = Looper.myLooper();? ? ? ? if (mLooper == null) {? ? ? ? ? ? throw new RuntimeException(? ? ? ? ? ? ? ? "Can't create handler inside thread that has not called Looper.prepare()");? ? ? ? }? ? ? ? mQueue = mLooper.mQueue;? ? ? ? mCallback = callback;? ? ? ? mAsynchronous = async;? ? }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152通過源碼可以看到Handler的無參構造函數調用了兩個參數的構造函數,而在兩個參數的構造函數中就是將一些變量進行賦值。
??看下下面的代碼
?mLooper = Looper.myLooper();? ? ? ? if (mLooper == null) {? ? ? ? ? ? throw new RuntimeException(? ? ? ? ? ? ? ? "Can't create handler inside thread that has not called Looper.prepare()");? ? ? ? }12345這里是通過Looper中的myLooper方法來獲得Looper實例的,如果Looper為null的話就會拋異常,拋出的異常內容翻譯過來就是
無法在未調用Looper.prepare()的線程內創建handler
從這句話中,我們可以知道,在調用Looper.myLooper()之前必須要先調用Looper.prepare()方法,現在來看下prepare方法中的內容,如下
?/** Initialize the current thread as a looper.? ? ? * This gives you a chance to create handlers that then reference? ? ? * this looper, before actually starting the loop. Be sure to call? ? ? * {@link #loop()} after calling this method, and end it by calling? ? ? * {@link #quit()}.? ? ? */? ? 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));? ? }12345678910111213141516從上面代碼中可以看到,prepare()方法調用了prepare(boolean quitAllowed)方法,prepare(boolean quitAllowed) 方法中則是實例化了一個Looper,然后將Looper設置進sThreadLocal中,到了這里就有必要了解一下ThreadLocalle。
什么是ThreadLocalThreadLocal 為解決多線程程序的并發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程序。當使用ThreadLocal 維護變量時,ThreadLocal 為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
如果看完上面這段話還是搞不明白ThreadLocal有什么用,那么可以看下下面代碼運行的結果,相信看下結果你就會明白ThreadLocal有什么作用了。
public class MainActivity extends AppCompatActivity {? ??? ? private static final String TAG = "MainActivity";? ? private ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();? ??? ? @SuppressLint("HandlerLeak")? ? private Handler mHandler = new Handler(){? ? ? ? @Override? ? ? ? public void handleMessage(Message msg) {? ? ? ? ? ? super.handleMessage(msg);? ? ? ? ? ? if (msg.what == 1) {? ? ? ? ? ? ? ? Log.d(TAG, "onCreate: "+mThreadLocal.get());? ? ? ? ? ? }? ? ? ? }? ? };
? ? @Override? ? protected void onCreate(Bundle savedInstanceState) {? ? ? ? super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? mThreadLocal.set(5);
? ? ? ? Thread1 thread1 = new Thread1();? ? ? ? thread1.start();
? ? ? ? Thread2 thread2 = new Thread2();? ? ? ? thread2.start();
? ? ? ? Thread3 thread3 = new Thread3();? ? ? ? thread3.start();
? ? ? ? new Thread(new Runnable() {? ? ? ? ? ? @Override? ? ? ? ? ? public void run() {? ? ? ? ? ? ? ? try {? ? ? ? ? ? ? ? ? ? Thread.sleep(2000);? ? ? ? ? ? ? ? ? ? mHandler.sendEmptyMessage(1);? ? ? ? ? ? ? ? } catch (InterruptedException e) {? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }).start();? ? }
? ? class Thread1 extends Thread {
? ? ? ? @Override? ? ? ? public void run() {? ? ? ? ? ? super.run();? ? ? ? ? ? mThreadLocal.set(1);? ? ? ? ? ? Log.d(TAG, "mThreadLocal1: "+ mThreadLocal.get());? ? ? ? }? ? }
? ? class Thread2 extends Thread {
? ? ? ? @Override? ? ? ? public void run() {? ? ? ? ? ? super.run();? ? ? ? ? ? mThreadLocal.set(2);? ? ? ? ? ? Log.d(TAG, "mThreadLocal2: "+ mThreadLocal.get());? ? ? ? }? ? }
? ? class Thread3 extends Thread {
? ? ? ? @Override? ? ? ? public void run() {? ? ? ? ? ? super.run();? ? ? ? ? ? mThreadLocal.set(3);? ? ? ? ? ? Log.d(TAG, "mThreadLocal3: "+ mThreadLocal.get());? ? ? ? }? ? }}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475看下這段代碼運行之后打印的log
可以看到雖然在不同的線程中對同一個mThreadLocal中的值進行了更改,但最后仍可以正確拿到當前線程中mThreadLocal中的值。由此我們可以得出結論ThreadLocal.set方法設置的值是與當前線程進行綁定了的。
??知道了ThreadLocal.set方法的作用,則Looper.prepare方法就是將Looper與當前線程進行綁定(當前線程就是調用Looper.prepare方法的線程)。
??文章到了這里我們可以知道以下幾點信息了
在對Handler進行實例化的時候,會對一些變量進行賦值。對Looper進行賦值是通過Looper.myLooper方法,但在調用這句代碼之前必須已經調用了Looper.prepare方法。Looper.prepare方法的作用就是將實例化的Looper與當前的線程進行綁定。這里就又出現了一個問題:在調用Looper.myLooper方法之前必須必須已經調用了Looper.prepare方法,即在實例化Handler之前就要調用Looper.prepare方法,但是我們平常在主線程中使用Handler的時候并沒有調用Looper.prepare方法呀!這是怎么回事呢?
??其實,在主線程中Android系統已經幫我們調用了Looper.prepare方法,可以看下ActivityThread類中的main方法,代碼如下
?public static void main(String[] args) {? ? ? ? Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
? ? ? ? // CloseGuard defaults to true and can be quite spammy.? We? ? ? ? // disable it here, but selectively enable it later (via? ? ? ? // StrictMode) on debug builds, but using DropBox, not logs.? ? ? ? CloseGuard.setEnabled(false);
? ? ? ? Environment.initForCurrentUser();
? ? ? ? // Set the reporter for event logging in libcore? ? ? ? EventLogger.setReporter(new EventLoggingReporter());
? ? ? ? // Make sure TrustedCertificateStore looks in the right place for CA certificates? ? ? ? final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());? ? ? ? TrustedCertificateStore.setDefaultUserDirectory(configDir);
? ? ? ? Process.setArgV0("<pre-initialized>");
? ? ? ? Looper.prepareMainLooper();
? ? ? ? ActivityThread thread = new ActivityThread();? ? ? ? thread.attach(false);
? ? ? ? if (sMainThreadHandler == null) {? ? ? ? ? ? sMainThreadHandler = thread.getHandler();? ? ? ? }
? ? ? ? if (false) {? ? ? ? ? ? Looper.myLooper().setMessageLogging(new? ? ? ? ? ? ? ? ? ? LogPrinter(Log.DEBUG, "ActivityThread"));? ? ? ? }
? ? ? ? // End of event ActivityThreadMain.? ? ? ? Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);? ? ? ? Looper.loop();
? ? ? ? throw new RuntimeException("Main thread loop unexpectedly exited");? ? }123456789101112131415161718192021222324252627282930313233343536373839上面的代碼中有一句
Looper.prepareMainLooper();1這句話的實質就是調用了Looper的prepare方法,代碼如下
?public static void prepareMainLooper() {? ? ? ? prepare(false);//這里調用了prepare方法? ? ? ? synchronized (Looper.class) {? ? ? ? ? ? if (sMainLooper != null) {? ? ? ? ? ? ? ? throw new IllegalStateException("The main Looper has already been prepared.");? ? ? ? ? ? }? ? ? ? ? ? sMainLooper = myLooper();? ? ? ? }? ? }123456789到這里就解決了,為什么我們在主線程中使用Handler之前沒有調用Looper.prepare方法的問題了。
??讓我們再回到Handler的構造方法中,看下
mLooper = Looper.myLooper();1myLooper()方法中代碼如下
/**? ? ?* Return the Looper object associated with the current thread.? Returns? ? ?* null if the calling thread is not associated with a Looper.? ? ?*/? ? public static @Nullable Looper myLooper() {? ? ? ? return sThreadLocal.get();? ? }1234567其實就是從當前線程中的ThreadLocal中取出Looper實例。
??再看下Handler的構造方法中的
mQueue = mLooper.mQueue;1這句代碼。這句代碼就是拿到Looper中的mQueue這個成員變量,然后再賦值給Handler中的mQueue,下面看下Looper中的代碼
?final MessageQueue mQueue;? ??private Looper(boolean quitAllowed) {? ? ? ? mQueue = new MessageQueue(quitAllowed);? ? ? ? mThread = Thread.currentThread();? ? }123456同過上面的代碼,我們可以知道mQueue就是MessageQueue,在我們調用Looper.prepare方法時就將mQueue實例化了。
Handler的sendMessage方法都做了什么??還記得文章開始時的兩個問題嗎?
Handler明明是在子線程中發的消息怎么會跑到主線程中了呢?Handler的發送消息handleMessage又是怎么接收到的呢?下面就分析一下Handler的sendMessage方法都做了什么,看代碼
public final boolean sendMessage(Message msg)? ? {? ? ? ? return sendMessageDelayed(msg, 0);? ? }
public final boolean sendMessageDelayed(Message msg, long delayMillis)? ? {? ? ? ? if (delayMillis < 0) {? ? ? ? ? ? delayMillis = 0;? ? ? ? }? ? ? ? return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);? ? }
/**? ? ?* Enqueue a message into the message queue after all pending messages? ? ?* before the absolute time (in milliseconds) <var>uptimeMillis</var>.? ? ?* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>? ? ?* Time spent in deep sleep will add an additional delay to execution.? ? ?* You will receive it in {@link #handleMessage}, in the thread attached? ? ?* to this handler.? ? ?*?? ? ?* @param uptimeMillis The absolute time at which the message should be? ? ?*? ? ? ? ?delivered, using the? ? ?*? ? ? ? ?{@link android.os.SystemClock#uptimeMillis} time-base.? ? ?*? ? ? ? ?? ? ?* @return Returns true if the message was successfully placed in to the?? ? ?*? ? ? ? ?message queue.? Returns false on failure, usually because the? ? ?*? ? ? ? ?looper processing the message queue is exiting.? Note that a? ? ?*? ? ? ? ?result of true does not mean the message will be processed -- if? ? ?*? ? ? ? ?the looper is quit before the delivery time of the message? ? ?*? ? ? ? ?occurs then the message will be dropped.? ? ?*/? ? public boolean sendMessageAtTime(Message msg, long uptimeMillis) {? ? ? ? MessageQueue queue = mQueue;? ? ? ? if (queue == null) {? ? ? ? ? ? RuntimeException e = new RuntimeException(? ? ? ? ? ? ? ? ? ? this + " sendMessageAtTime() called with no mQueue");? ? ? ? ? ? Log.w("Looper", e.getMessage(), e);? ? ? ? ? ? return false;? ? ? ? }? ? ? ? return enqueueMessage(queue, msg, uptimeMillis);? ? }123456789101112131415161718192021222324252627282930313233343536373839404142由上面的代碼可以看出,Handler的sendMessage方法最后調用了sendMessageAtTime這個方法,其實,無論時sendMessage、sendEmptyMessage等方法最終都是調用sendMessageAtTime。可以看到sendMessageAtTime這個方法最后返回的是*enqueueMessage(queue, msg, uptimeMillis);*下面看下這個方法,代碼如下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {? ? ? ? msg.target = this;? ? ? ? if (mAsynchronous) {? ? ? ? ? ? msg.setAsynchronous(true);? ? ? ? }? ? ? ? return queue.enqueueMessage(msg, uptimeMillis);? ? }1234567這里有一句代碼非常重要,
?msg.target = this;1這句代碼就是將當前的Handler賦值給了Message中的target變量。這樣,就將每個調用sendMessage方法的Handler與Message進行了綁定。
enqueueMessage方法最后返回的是**queue.enqueueMessage(msg, uptimeMillis);**也就是調用了MessageQueue中的enqueueMessage方法,下面看下MessageQueue中的enqueueMessage方法,代碼如下
boolean enqueueMessage(Message msg, long when) {? ? ? ? if (msg.target == null) {? ? ? ? ? ? throw new IllegalArgumentException("Message must have a target.");? ? ? ? }? ? ? ? if (msg.isInUse()) {? ? ? ? ? ? throw new IllegalStateException(msg + " This message is already in use.");? ? ? ? }
? ? ? ? synchronized (this) {? ? ? ? ? ? if (mQuitting) {? ? ? ? ? ? ? ? IllegalStateException e = new IllegalStateException(? ? ? ? ? ? ? ? ? ? ? ? msg.target + " sending message to a Handler on a dead thread");? ? ? ? ? ? ? ? Log.w(TAG, e.getMessage(), e);? ? ? ? ? ? ? ? msg.recycle();? ? ? ? ? ? ? ? return false;? ? ? ? ? ? }
? ? ? ? ? ? msg.markInUse();? ? ? ? ? ? msg.when = when;? ? ? ? ? ? Message p = mMessages;? ? ? ? ? ? boolean needWake;? ? ? ? ? ? if (p == null || when == 0 || when < p.when) {? ? ? ? ? ? ? ? // New head, wake up the event queue if blocked.? ? ? ? ? ? ? ? msg.next = p;? ? ? ? ? ? ? ? mMessages = msg;? ? ? ? ? ? ? ? needWake = mBlocked;? ? ? ? ? ? } else {? ? ? ? ? ? ? ? // Inserted within the middle of the queue.? Usually we don't have to wake? ? ? ? ? ? ? ? // up the event queue unless there is a barrier at the head of the queue? ? ? ? ? ? ? ? // and the message is the earliest asynchronous message in the queue.? ? ? ? ? ? ? ? needWake = mBlocked && p.target == null && msg.isAsynchronous();? ? ? ? ? ? ? ? Message prev;? ? ? ? ? ? ? ? for (;;) {? ? ? ? ? ? ? ? ? ? prev = p;? ? ? ? ? ? ? ? ? ? p = p.next;? ? ? ? ? ? ? ? ? ? if (p == null || when < p.when) {? ? ? ? ? ? ? ? ? ? ? ? break;? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? if (needWake && p.isAsynchronous()) {? ? ? ? ? ? ? ? ? ? ? ? needWake = false;? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? msg.next = p; // invariant: p == prev.next? ? ? ? ? ? ? ? prev.next = msg;? ? ? ? ? ? }
? ? ? ? ? ? // We can assume mPtr != 0 because mQuitting is false.? ? ? ? ? ? if (needWake) {? ? ? ? ? ? ? ? nativeWake(mPtr);? ? ? ? ? ? }? ? ? ? }? ? ? ? return true;? ? }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253上面的代碼就是將消息放進消息隊列中,如果消息已成功放入消息隊列,則返回true。失敗時返回false,而失敗的原因通常是因為處理消息隊列正在退出。代碼分析到這里可以得出以下兩點結論了
Handler在sendMessage時會將自己設置給Message的target變量即將自己與發送的消息綁定。Handler的sendMessage是將Message放入MessageQueue中。到了這里已經知道Handler的sendMessage是將消息放進MessageQueue中,那么又是怎樣從MessageQueue中拿到消息的呢?想要知道答案請繼續閱讀。
怎樣從MessageQueue中獲取Message??在文章的前面,貼出了ActivityThread類中的main方法的代碼,不知道細心的你有沒有注意到,在main方法的結尾處調用了一句代碼
Looper.loop();1好了,現在可以看看*Looper.loop();*這句代碼到底做了什么了loop方法中的代碼如下
/**? ? ?* Run the message queue in this thread. Be sure to call? ? ?* {@link #quit()} to end the loop.? ? ?*/? ? public static void loop() {? ? ? ? final Looper me = myLooper();//通過myLooper方法拿到與主線程綁定的Looper? ? ? ? if (me == null) {? ? ? ? ? ? throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");? ? ? ? }? ? ? ? final MessageQueue queue = me.mQueue;//從Looper中得到MessageQueue
? ? ? ? // Make sure the identity of this thread is that of the local process,? ? ? ? // and keep track of what that identity token actually is.? ? ? ? Binder.clearCallingIdentity();? ? ? ? final long ident = Binder.clearCallingIdentity();
? ? ? ? //開始死循環? ? ? ? for (;;) {? ? ? ? ? ? //從消息隊列中不斷取出消息? ? ? ? ? ? Message msg = queue.next(); // might block? ? ? ? ? ? if (msg == null) {? ? ? ? ? ? ? ? // No message indicates that the message queue is quitting.? ? ? ? ? ? ? ? return;? ? ? ? ? ? }
? ? ? ? ? ? // This must be in a local variable, in case a UI event sets the logger? ? ? ? ? ? final Printer logging = me.mLogging;? ? ? ? ? ? if (logging != null) {? ? ? ? ? ? ? ? logging.println(">>>>> Dispatching to " + msg.target + " " +? ? ? ? ? ? ? ? ? ? ? ? msg.callback + ": " + msg.what);? ? ? ? ? ? }
? ? ? ? ? ? final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
? ? ? ? ? ? final long traceTag = me.mTraceTag;? ? ? ? ? ? if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {? ? ? ? ? ? ? ? Trace.traceBegin(traceTag, msg.target.getTraceName(msg));? ? ? ? ? ? }? ? ? ? ? ? final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();? ? ? ? ? ? final long end;? ? ? ? ? ? try {? ? ? ? ? ? ? ? //這句代碼是重點? ? ? ? ? ? ? ? msg.target.dispatchMessage(msg);? ? ? ? ? ? ? ? end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();? ? ? ? ? ? } finally {? ? ? ? ? ? ? ? if (traceTag != 0) {? ? ? ? ? ? ? ? ? ? Trace.traceEnd(traceTag);? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? if (slowDispatchThresholdMs > 0) {? ? ? ? ? ? ? ? final long time = end - start;? ? ? ? ? ? ? ? if (time > slowDispatchThresholdMs) {? ? ? ? ? ? ? ? ? ? Slog.w(TAG, "Dispatch took " + time + "ms on "? ? ? ? ? ? ? ? ? ? ? ? ? ? + Thread.currentThread().getName() + ", h=" +? ? ? ? ? ? ? ? ? ? ? ? ? ? msg.target + " cb=" + msg.callback + " msg=" + msg.what);? ? ? ? ? ? ? ? }? ? ? ? ? ? }
? ? ? ? ? ? if (logging != null) {? ? ? ? ? ? ? ? logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);? ? ? ? ? ? }
? ? ? ? ? ? // Make sure that during the course of dispatching the? ? ? ? ? ? // identity of the thread wasn't corrupted.? ? ? ? ? ? final long newIdent = Binder.clearCallingIdentity();? ? ? ? ? ? if (ident != newIdent) {? ? ? ? ? ? ? ? Log.wtf(TAG, "Thread identity changed from 0x"? ? ? ? ? ? ? ? ? ? ? ? + Long.toHexString(ident) + " to 0x"? ? ? ? ? ? ? ? ? ? ? ? + Long.toHexString(newIdent) + " while dispatching to "? ? ? ? ? ? ? ? ? ? ? ? + msg.target.getClass().getName() + " "? ? ? ? ? ? ? ? ? ? ? ? + msg.callback + " what=" + msg.what);? ? ? ? ? ? }
? ? ? ? ? ? msg.recycleUnchecked();? ? ? ? }? ? }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576上面的代碼,我已經進行了部分注釋,這里有一句代碼非常重要
?msg.target.dispatchMessage(msg);1執行到這句代碼,說明已經從消息隊列中拿到了消息,還記得msg.target嗎?就是Message中的target變量呀!也就是發送消息的那個Handler,所以這句代碼的本質就是調用了Handler中的dispatchMessage(msg)方法,代碼分析到這里是不是有點小激動了呢!穩住!下面看下dispatchMessage(msg)這個方法,代碼如下
/**? ? ?* Handle system messages here.? ? ?*/? ? public void dispatchMessage(Message msg) {? ? ? ? if (msg.callback != null) {? ? ? ? ? ? handleCallback(msg);? ? ? ? } else {? ? ? ? ? ? if (mCallback != null) {? ? ? ? ? ? ? ? if (mCallback.handleMessage(msg)) {? ? ? ? ? ? ? ? ? ? return;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? handleMessage(msg);? ? ? ? }? ? }123456789101112131415現在來一句句的來分析上面的代碼,先看下這句
if (msg.callback != null) {? ? ? ? ? ? handleCallback(msg);? ? ? ? }?123msg.callback就是Runnable對象,當msg.callback不為null時會調用 handleCallback(msg)方法,先來看下 handleCallback(msg)方法,代碼如下
?private static void handleCallback(Message message) {? ? ? ? message.callback.run();? ? }123上面的代碼就是調用了Runnable的run方法。那什么情況下**if (msg.callback != null)**這個條件成立呢!還記得使用Handler的另一種方法嗎?就是調用Handler的post方法呀!這里說明一下,使用Handler其實是有兩種方法的
使用Handler的sendMessage方法,最后在handleMessage(Message msg)方法中來處理消息。使用Handler的post方法,最后在Runnable的run方法中來處理,代碼如下public class MainActivity extends AppCompatActivity implements View.OnClickListener {? ? private Button mTimeCycle,mStopCycle;? ? private Runnable mRunnable;
? ? @Override? ? protected void onCreate(Bundle savedInstanceState) {? ? ? ? super.onCreate(savedInstanceState);? ? ? ? setContentView(R.layout.activity_main);? ? ? ? initView();? ? }
? ? private void initView() {? ? ? ? mTimeCycle = findViewById(R.id.btn_time_cycle);? ? ? ? mTimeCycle.setOnClickListener(this);? ? ? ? mStopCycle = findViewById(R.id.btn_stop_cycle);? ? ? ? mStopCycle.setOnClickListener(this);
? ? ? ? mRunnable = new Runnable() {? ? ? ? ? ? @Override? ? ? ? ? ? public void run() {? ? ? ? ? ? ? ? Toast.makeText(MainActivity.this, "正在循環!!!", Toast.LENGTH_SHORT).show();? ? ? ? ? ? ? ? mHandler.postDelayed(mRunnable, 1000);? ? ? ? ? ? }? ? ? ? };? ? }
? ? @Override? ? public void onClick(View v) {? ? ? ? switch (v.getId()) {? ? ? ? ? ? case R.id.btn_time_cycle:? ? ? ? ? ? ? ? mHandler.post(mRunnable);? ? ? ? ? ? ? ? break;? ? ? ? ? ? case R.id.btn_stop_cycle:? ? ? ? ? ? ? ? mHandler.removeCallbacks(mRunnable);? ? ? ? ? ? ? ? break;? ? ? ? }? ? }}123456789101112131415161718192021222324252627282930313233343536373839第一種方法,我們已經分析了,下面來分析一下第二種使用方式的原理,先看下Handler的post的方法做了什么,代碼如下
/**? ? ?* Causes the Runnable r to be added to the message queue.? ? ?* The runnable will be run on the thread to which this handler is?? ? ?* attached.?? ? ?*??? ? ?* @param r The Runnable that will be executed.? ? ?*?? ? ?* @return Returns true if the Runnable was successfully placed in to the?? ? ?*? ? ? ? ?message queue.? Returns false on failure, usually because the? ? ?*? ? ? ? ?looper processing the message queue is exiting.? ? ?*/? ? public final boolean post(Runnable r)? ? {? ? ? ?return? sendMessageDelayed(getPostMessage(r), 0);? ? }
?private static Message getPostMessage(Runnable r) {? ? ? ? Message m = Message.obtain();? ? ? ? m.callback = r;? ? ? ? return m;? ? }123456789101112131415161718192021由上面的代碼不難看出,post方法最終也是將Runnable封裝成消息,然后將消息放進MessageQueue中。下面繼續分析dispatchMessage方法中的代碼
else {? ? //if中的代碼其實是和if (msg.callback != null) {handleCallback(msg);}?? ? //原理差不多的,只不過mCallback是Handler中的成員變量。? ? ? ? ? ? if (mCallback != null) {? ? ? ? ? ? ? ? if (mCallback.handleMessage(msg)) {? ? ? ? ? ? ? ? ? ? return;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? //當上面的條件都不成立時,就會調用這句代碼? ? ? ? ? ? handleMessage(msg);? ? ? ? }1234567891011上面的代碼就不分析了,我已經在代碼中進行了注釋,下面再看下**handleMessage(msg)**這個方法,代碼如下
/**? ? ?* Subclasses must implement this to receive messages.? ? ?*/? ? public void handleMessage(Message msg) {? ? }12345其實,他就是一個空方法,具體的代碼讓我們自己重寫這個方法進行處理。代碼分析到這里,已經可以給出下面問題的答案了。
Handler明明是在子線程中發的消息怎么會跑到主線程中了呢?Handler的發送消息handleMessage又是怎么接收到的呢?在子線程中Handler在發送消息的時候已經把自己與當前的message進行了綁定,在通過Looper.loop()開啟輪詢message的時候,當獲得message的時候會調用 與之綁定的Handler的**handleMessage(Message msg)**方法,由于Handler是在主線程創建的,所以自然就由子線程切換到了主線程。
總結??上面已經嗯將Handler的源碼分析了一遍,現在來進行一些總結:
1、Handler的工作原理??在使用Handler之前必須要調用Looper.prepare()這句代碼,這句代碼的作用是將Looper與當前的線程進行綁定,在實例化Handler的時候,通過Looper.myLooper()獲取Looper,然后再獲得Looper中的MessageQueue。在子線程中調用Handler的sendMessage方法就是將Message放入MessageQueue中,然后調用Looper.loop()方法來從MessageQueue中取出Message,在取到Message的時候,執行 **msg.target.dispatchMessage(msg);**這句代碼,這句代碼就是從當前的Message中取出Handler然后執行Handler的handleMessage方法。
2、Handler、Message、MessageQueue以及Looper之間的關系??在介紹它們之間的關系之前,先說一下它們各自的作用。
Handler:負責發送和處理消息。Message:用來攜帶需要的數據。MessageQueue:消息隊列,隊列里面的內容就是Message。Looper:消息輪巡器,負責不停的從MessageQueue中取Message。它們的關系如下圖(圖片來源于網上)
3、在子線程中使用Handler??在子線程中使用Handler的方式如下
class LooperThread extends Thread {? ? public Handler mHandler;? ? public void run() {? ? ? ? Looper.prepare();? ? ? ? mHandler = new Handler() {? ? ? ? ? ? public void handleMessage(Message msg) {? ? ? ? ? ? ? ? // process incoming messages here? ? ? ? ? ? }? ? ? ? };? ? ? ? Looper.loop();? ? }}123456789101112上面的代碼來自官方的源碼。
結束語??本文將Handler的機制詳細講解了一遍,包括在面試中有關Handler的一些問題,在文章中也能找到答案。順便說下閱讀代碼應該注意的地方,在分析源碼之前應該知道你分析代碼的目的,就是你為了得到什么答案而分析代碼;在分析代碼時切記要避輕就重,不要想著要搞懂每句代碼做了什么,要找準大方向。文中的代碼已上傳到GitHub,可以在這里獲取,與Handler有關的源碼在我上傳的源碼的handler包中。
ps: 歷史文章中有干貨哦!
轉載請注明出處:www.wizardev.cn---------------------?作者:wizardev?來源:CSDN?原文:https://blog.csdn.net/fnhfire_7030/article/details/79518819?版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
轉載于:https://www.cnblogs.com/Jeely/p/10949274.html
總結
以上是生活随笔為你收集整理的Android:异步处理之Handler+Thread的应用(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爱心动画c语言,jquery+html5
- 下一篇: notepad python 单步调试_