日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Handler的初級(jí)、中級(jí)、高級(jí)問(wèn)法,你都掌握了嗎?

handler是Android中的消息處理機(jī)制,是一種線程間通信的解決方案,同時(shí)你也可以理解為它天然的為我們?cè)谥骶€程創(chuàng)建一個(gè)隊(duì)列,隊(duì)列中的消息順序就是我們?cè)O(shè)置的延遲的時(shí)間,如果你想在Android中實(shí)現(xiàn)一個(gè)隊(duì)列的功能,不妨第一時(shí)間考慮一下它。本文分為三部分:

  •  Handler的源碼和常見(jiàn)問(wèn)題的解答

           1.  一個(gè)線程中最多有多少個(gè)Handler,Looper,MessageQueue?

           2.  Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死,會(huì)耗費(fèi)大量資源嗎?

           3.  子線程的如何更新UI,比如Dialog,Toast等?系統(tǒng)為什么不建議子線程中更新UI?

           4.  主線程如何訪問(wèn)網(wǎng)絡(luò)?

           5.  如何處理Handler使用不當(dāng)造成的內(nèi)存泄漏?

           6.  Handler的消息優(yōu)先級(jí),有什么應(yīng)用場(chǎng)景?

           7.  主線程的Looper何時(shí)退出?能否手動(dòng)退出?

           8.  如何判斷當(dāng)前線程是安卓主線程?

           9.  正確創(chuàng)建Message實(shí)例的方式?

  •  Handler深層次問(wèn)題解答

           1.  ThreadLocal

           2.  epoll機(jī)制

           3.  Handle同步屏障機(jī)制

           4.  Handler的鎖相關(guān)問(wèn)題

           5.  Handler中的同步方法

  •  Handler在系統(tǒng)以及第三方框架的一些應(yīng)用

           1.  HandlerThread

           2.  IntentService

           3.  如何打造一個(gè)不崩潰的APP

           4.  Glide中的運(yùn)用

Handler的源碼和常見(jiàn)問(wèn)題的解答

下面來(lái)看一下官方對(duì)其的定義: 

 
 
 
 
  1. A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread. 

大意就是Handler允許你發(fā)送Message/Runnable到線程的消息隊(duì)列(MessageQueue)中,每個(gè)Handler實(shí)例和一個(gè)線程以及那個(gè)線程的消息隊(duì)列相關(guān)聯(lián)。當(dāng)你創(chuàng)建一個(gè)Handler時(shí)應(yīng)該和一個(gè)Looper進(jìn)行綁定(主線程默認(rèn)已經(jīng)創(chuàng)建Looper了,子線程需要自己創(chuàng)建Looper),它向Looper的對(duì)應(yīng)的消息隊(duì)列傳送Message/Runnable同時(shí)在那個(gè)Looper所在線程處理對(duì)應(yīng)的Message/Runnable。下面這張圖就是Handler的工作流程

Handler工作流程圖

可以看到在Thread中,Looper的這個(gè)傳送帶其實(shí)就一個(gè)死循環(huán),它不斷的從消息隊(duì)列MessageQueue中不斷的取消息,最后交給Handler.dispatchMessage進(jìn)行消息的分發(fā),而Handler.sendXXX,Handler.postXXX這些方法把消息發(fā)送到消息隊(duì)列中MessageQueue,整個(gè)模式其實(shí)就是一個(gè)生產(chǎn)者-消費(fèi)者模式,源源不斷的生產(chǎn)消息,處理消息,沒(méi)有消息時(shí)進(jìn)行休眠。MessageQueue是一個(gè)由單鏈表構(gòu)成的優(yōu)先級(jí)隊(duì)列(取的都是頭部,所以說(shuō)是隊(duì)列)。

前面說(shuō)過(guò),當(dāng)你創(chuàng)建一個(gè)Handler時(shí)應(yīng)該和一個(gè)Looper進(jìn)行綁定(綁定也可以理解為創(chuàng)建,主線程默認(rèn)已經(jīng)創(chuàng)建Looper了,子線程需要自己創(chuàng)建Looper),因此我們先來(lái)看看主線程中是如何處理的: 

 
 
 
 
  1. //ActivityThread.java  
  2. public static void main(String[] args) {  
  3.     ···  
  4.     Looper.prepareMainLooper();  
  5.     ··· 
  6.      ActivityThread thread = new ActivityThread();  
  7.     thread.attach(false, startSeq);  
  8.     if (sMainThreadHandler == null) {  
  9.         sMainThreadHandler = thread.getHandler();  
  10.     }  
  11.     if (false) {  
  12.         Looper.myLooper().setMessageLogging(new  
  13.                 LogPrinter(Log.DEBUG, "ActivityThread"));  
  14.     }  
  15.     // End of event ActivityThreadMain.  
  16.     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  17.     Looper.loop();  
  18.     throw new RuntimeException("Main thread loop unexpectedly exited");  

可以看到在ActivityThread中的main方法中,我們先調(diào)用了Looper.prepareMainLooper()方法,然后獲取當(dāng)前線程的Handler,最后調(diào)用Looper.loop()。先來(lái)看一下Looper.prepareMainLooper()方法 

 
 
 
 
  1. //Looper.java    
  2. /**  
  3. * Initialize the current thread as a looper, marking it as an  
  4. * application's main looper. The main looper for your application  
  5. * is created by the Android environment, so you should never need  
  6. * to call this function yourself.  See also: {@link #prepare()}  
  7. */  
  8. public static void prepareMainLooper() {  
  9.      prepare(false);  
  10.      synchronized (Looper.class) {  
  11.          if (sMainLooper != null) {  
  12.              throw new IllegalStateException("The main Looper has already been prepared.");  
  13.          } 
  14.           sMainLooper = myLooper();  
  15.      }  
  16. }  
  17. //prepare  
  18. private static void prepare(boolean quitAllowed) {  
  19.         if (sThreadLocal.get() != null) {  
  20.             throw new RuntimeException("Only one Looper may be created per thread");  
  21.         }  
  22.         sThreadLocal.set(new Looper(quitAllowed));  

可以看到在Looper.prepareMainLooper()方法中創(chuàng)建了當(dāng)前線程的Looper,同時(shí)將Looper實(shí)例存放到線程局部變量sThreadLocal(ThreadLocal)中,也就是每個(gè)線程有自己的Looper。在創(chuàng)建Looper的時(shí)候也創(chuàng)建了該線程的消息隊(duì)列,可以看到prepareMainLooper會(huì)判斷sMainLooper是否有值,如果調(diào)用多次,就會(huì)拋出異常,所以也就是說(shuō)主線程的Looper和MessageQueue只會(huì)有一個(gè)。同理子線程中調(diào)用Looper.prepare()時(shí),會(huì)調(diào)用prepare(true)方法,如果多次調(diào)用,也會(huì)拋出每個(gè)線程只能由一個(gè)Looper的異常,總結(jié)起來(lái)就是每個(gè)線程中只有一個(gè)Looper和MessageQueue。 

 
 
 
 
  1. //Looper.java  
  2. private Looper(boolean quitAllowed) {  
  3.     mQueue = new MessageQueue(quitAllowed);  
  4.     mThread = Thread.currentThread();  

再來(lái)看看主線程sMainThreadHandler = thread.getHandler(),getHandler獲取到的實(shí)際上就是mH這個(gè)Handler。 

 
 
 
 
  1. //ActivityThread.java  
  2. final H mH = new H();   
  3. @UnsupportedAppUsage  
  4.   final Handler getHandler() {  
  5.     return mH;  

mH這個(gè)Handler是ActivityThread的內(nèi)部類,通過(guò)查看handMessage方法,可以看到這個(gè)Handler處理四大組件,Application等的一些消息,比如創(chuàng)建Service,綁定Service的一些消息。 

 
 
 
 
  1. //ActivityThread.java  
  2. class H extends Handler {  
  3.     ···  
  4.     public void handleMessage(Message msg) {  
  5.         if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));  
  6.         switch (msg.what) {  
  7.             case BIND_APPLICATION:  
  8.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");  
  9.                 AppBindData data = (AppBindData)msg.obj;  
  10.                 handleBindApplication(data);  
  11.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  12.                 break;  
  13.             case EXIT_APPLICATION:  
  14.                 if (mInitialApplication != null) {  
  15.                     mInitialApplication.onTerminate();  
  16.                 }  
  17.                 Looper.myLooper().quit();  
  18.                 break;  
  19.             case RECEIVER:  
  20.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");  
  21.                 handleReceiver((ReceiverData)msg.obj);  
  22.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  23.                 break;  
  24.             case CREATE_SERVICE:  
  25.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));  
  26.                 handleCreateService((CreateServiceData)msg.obj);  
  27.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  28.                 break;  
  29.             case BIND_SERVICE:  
  30.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");  
  31.                 handleBindService((BindServiceData)msg.obj);  
  32.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  33.                 break;  
  34.             case UNBIND_SERVICE:  
  35.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind"); 
  36.                 handleUnbindService((BindServiceData)msg.obj);  
  37.                 schedulePurgeIdler();  
  38.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  39.                 break;  
  40.             case SERVICE_ARGS:  
  41.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));  
  42.                 handleServiceArgs((ServiceArgsData)msg.obj);  
  43.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  44.                 break;  
  45.             case STOP_SERVICE:  
  46.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");  
  47.                 handleStopService((IBinder)msg.obj);  
  48.                 schedulePurgeIdler();  
  49.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  50.                 break;  
  51.             ···  
  52.             case APPLICATION_INFO_CHANGED:  
  53.                 mUpdatingSystemConfig = true;  
  54.                 try {  
  55.                     handleApplicationInfoChanged((ApplicationInfo) msg.obj);  
  56.                 } finally {  
  57.                     mUpdatingSystemConfig = false;  
  58.                 }  
  59.                 break;  
  60.             case RUN_ISOLATED_ENTRY_POINT:  
  61.                 handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,  
  62.                         (String[]) ((SomeArgs) msg.obj).arg2);  
  63.                 break;  
  64.             case EXECUTE_TRANSACTION:  
  65.                 final ClientTransaction transaction = (ClientTransaction) msg.obj;  
  66.                 mTransactionExecutor.execute(transaction);  
  67.                 if (isSystem()) {  
  68.                     // Client transactions inside system process are recycled on the client side  
  69.                     // instead of ClientLifecycleManager to avoid being cleared before this  
  70.                     // message is handled.  
  71.                     transaction.recycle();  
  72.                 }  
  73.                 // TODO(lifecycler): Recycle locally scheduled transactions.  
  74.                 break;  
  75.             case RELAUNCH_ACTIVITY:  
  76.                 handleRelaunchActivityLocally((IBinder) msg.obj);  
  77.                 break;  
  78.             case PURGE_RESOURCES:  
  79.                 schedulePurgeIdler();  
  80.                 break;  
  81.         }  
  82.         Object obj = msg.obj;  
  83.         if (obj instanceof SomeArgs) {  
  84.             ((SomeArgs) obj).recycle();  
  85.         }  
  86.         if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));  
  87.     }  

最后我們查看Looper.loop()方法 

 
 
 
 
  1. //Looper.java  
  2. public static void loop() {  
  3.     //獲取ThreadLocal中的Looper  
  4.     final Looper me = myLooper();  
  5.     ···  
  6.     final MessageQueue queue = me.mQueue;  
  7.     ···  
  8.     for (;;) { //死循環(huán)  
  9.         //獲取消息  
  10.         Message msg = queue.next(); // might block  
  11.         if (msg == null) {  
  12.             // No message indicates that the message queue is quitting.  
  13.             return;  
  14.         }  
  15.         ···  
  16.         msg.target.dispatchMessage(msg);  
  17.         ···  
  18.         //回收復(fù)用    
  19.         msg.recycleUnchecked();  
  20.     }  

在loop方法中是一個(gè)死循環(huán),在這里從消息隊(duì)列中不斷的獲取消息queue.next(),然后通過(guò)Handler(msg.target)進(jìn)行消息的分發(fā),其實(shí)并沒(méi)有什么具體的綁定,因?yàn)镠andler在每個(gè)線程中對(duì)應(yīng)只有一個(gè)Looper和消息隊(duì)列MessageQueue,自然要靠它來(lái)處理,也就是是調(diào)用Looper.loop()方法。在Looper.loop()的死循環(huán)中不斷的取消息,最后回收復(fù)用。

這里要強(qiáng)調(diào)一下Message中的參數(shù)target(Handler),正是這個(gè)變量,每個(gè)Message才能找到對(duì)應(yīng)的Handler進(jìn)行消息分發(fā),讓多個(gè)Handler同時(shí)工作。

再來(lái)看看子線程中是如何處理的,首先在子線程中創(chuàng)建一個(gè)Handler并發(fā)送Runnable 

 
 
 
 
  1. @Override  
  2.     protected void onCreate(@Nullable Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.activity_three); 
  5.          new Thread(new Runnable() {  
  6.             @Override  
  7.             public void run() {  
  8.                 new Handler().post(new Runnable() {  
  9.                     @Override  
  10.                     public void run() {  
  11.                         Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();  
  12.                     }  
  13.                 });  
  14.             }  
  15.         }).start();  
  16.     } 

運(yùn)行后可以看到錯(cuò)誤日志,可以看到提示我們需要在子線程中調(diào)用Looper.prepare()方法,實(shí)際上就是要?jiǎng)?chuàng)建一個(gè)Looper和你的Handler進(jìn)行“關(guān)聯(lián)”。   

 
 
 
 
  1. --------- beginning of crash  
  2. 020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2  
  3.    Process: com.jackie.testdialog, PID: 21122  
  4.    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()  
  5.        at android.os.Handler.(Handler.java:207)  
  6.        at android.os.Handler.(Handler.java:119)  
  7.        at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)  
  8.        at java.lang.Thread.run(Thread.java:919) 

添加Looper.prepare()創(chuàng)建Looper,同時(shí)調(diào)用Looper.loop()方法開(kāi)始處理消息。 

 
 
 
 
  1. @Override  
  2.   protected void onCreate(@Nullable Bundle savedInstanceState) {  
  3.       super.onCreate(savedInstanceState);  
  4.       setContentView(R.layout.activity_three);  
  5.       new Thread(new Runnable() {  
  6.           @Override  
  7.           public void run() {  
  8.               //創(chuàng)建Looper,MessageQueue  
  9.               Looper.prepare();  
  10.               new Handler().post(new Runnable() {  
  11.                   @Override  
  12.                   public void run() {  
  13.                       Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();  
  14.                   }  
  15.               });  
  16.               //開(kāi)始處理消息  
  17.               Looper.loop();  
  18.           }  
  19.       }).start();  
  20.   } 

這里需要注意在所有事情處理完成后應(yīng)該調(diào)用quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于循環(huán)等待的狀態(tài),因此不需要的時(shí)候終止Looper,調(diào)用Looper.myLooper().quit()。

看完上面的代碼可能你會(huì)有一個(gè)疑問(wèn),在子線程中更新UI(進(jìn)行Toast)不會(huì)有問(wèn)題嗎,我們Android不是不允許在子線程更新UI嗎,實(shí)際上并不是這樣的,在ViewRootImpl中的checkThread方法會(huì)校驗(yàn)mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構(gòu)造器中,也就是說(shuō)一個(gè)創(chuàng)建ViewRootImpl線程必須和調(diào)用checkThread所在的線程一致,UI的更新并非只能在主線程才能進(jìn)行。 

 
 
 
 
  1. void checkThread() {  
  2.     if (mThread != Thread.currentThread()) {  
  3.         throw new CalledFromWrongThreadException(  
  4.                 "Only the original thread that created a view hierarchy can touch its views.");  
  5.     }  

這里需要引入一些概念,Window是Android中的窗口,每個(gè)Activity和Dialog,Toast分別對(duì)應(yīng)一個(gè)具體的Window,Window是一個(gè)抽象的概念,每一個(gè)Window都對(duì)應(yīng)著一個(gè)View和一個(gè)ViewRootImpl,Window和View通過(guò)ViewRootImpl來(lái)建立聯(lián)系,因此,它是以View的形式存在的。我們來(lái)看一下Toast中的ViewRootImpl的創(chuàng)建過(guò)程,調(diào)用toast的show方法最終會(huì)調(diào)用到其handleShow方法 

 
 
 
 
  1. //Toast.java  
  2. public void handleShow(IBinder windowToken) {  
  3.     ···  
  4.     if (mView != mNextView) {  
  5.         // Since the notification manager service cancels the token right  
  6.         // after it notifies us to cancel the toast there is an inherent  
  7.         // race and we may attempt to add a window after the token has been  
  8.         // invalidated. Let us hedge against that.  
  9.         try {  
  10.             mWM.addView(mView, mParams); //進(jìn)行ViewRootImpl的創(chuàng)建  
  11.             trySendAccessibilityEvent();  
  12.         } catch (WindowManager.BadTokenException e) {  
  13.             /* ignore */  
  14.         }  
  15.     }  

這個(gè)mWM(WindowManager)的最終實(shí)現(xiàn)者是WindowManagerGlobal,其的addView方法中會(huì)創(chuàng)建ViewRootImpl,然后進(jìn)行root.setView(view, wparams, panelParentView),通過(guò)ViewRootImpl來(lái)更新界面并完成Window的添加過(guò)程。 

 
 
 
 
  1. //WindowManagerGlobal.java  
  2. root = new ViewRootImpl(view.getContext(), display); //創(chuàng)建ViewRootImpl  
  3.     view.setLayoutParams(wparams);  
  4.     mViews.add(view);  
  5.     mRoots.add(root);  
  6.     mParams.add(wparams);  
  7.     // do this last because it fires off messages to start doing things  
  8.     try {  
  9.         //ViewRootImpl  
  10.         root.setView(view, wparams, panelParentView);  
  11.     } catch (RuntimeException e) {  
  12.         // BadTokenException or InvalidDisplayException, clean up.  
  13.         if (index >= 0) {  
  14.             removeViewLocked(index, true);  
  15.         }  
  16.         throw e;  
  17.     }  

setView內(nèi)部會(huì)通過(guò)requestLayout來(lái)完成異步刷新請(qǐng)求,同時(shí)也會(huì)調(diào)用checkThread方法來(lái)檢驗(yàn)線程的合法性。 

 
 
 
 
  1. @Override  
  2. public void requestLayout() {  
  3.     if (!mHandlingLayoutInLayoutRequest) {  
  4.         checkThread();  
  5.         mLayoutRequested = true;  
  6.         scheduleTraversals();  
  7.     }  

因此,我們的ViewRootImpl的創(chuàng)建是在子線程,所以mThread的值也是子線程,同時(shí)我們的更新也是在子線程,所以不會(huì)產(chǎn)生異常,同時(shí)也可以參考這篇文章分析,寫(xiě)的非常詳細(xì)。同理下面的代碼也可以驗(yàn)證這個(gè)情況 

 
 
 
 
  1. //子線程中調(diào)用      
  2. public void showDialog(){  
  3.         new Thread(new Runnable() {  
  4.             @Override  
  5.             public void run() {  
  6.                 //創(chuàng)建Looper,MessageQueue  
  7.                 Looper.prepare();  
  8.                 new Handler().post(new Runnable() {  
  9.                     @Override  
  10.                     public void run() {  
  11.                         builder = new AlertDialog.Builder(HandlerActivity.this);  
  12.                         builder.setTitle("jackie");  
  13.                         alertDialog = builder.create();  
  14.                         alertDialog.show();  
  15.                         alertDialog.hide();  
  16.                     }  
  17.                 });  
  18.                 //開(kāi)始處理消息  
  19.                 Looper.loop();  
  20.             }  
  21.         }).start();  
  22.     } 

在子線程中調(diào)用showDialog方法,先調(diào)用alertDialog.show()方法,再調(diào)用alertDialog.hide()方法,hide方法只是將Dialog隱藏,并沒(méi)有做其他任何操作(沒(méi)有移除Window),然后再在主線程調(diào)用alertDialog.show();便會(huì)拋出Only the original thread that created a view hierarchy can touch its views異常了。 

 
 
 
 
  1. 2020-11-09 18:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main  
  2.     Process: com.jackie.testdialog, PID: 24819  
  3.     android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.  
  4.         at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)  
  5.         at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)  
  6.         at android.view.View.requestLayout(View.java:24454)  
  7.         at android.view.View.setFlags(View.java:15187)  
  8.         at android.view.View.setVisibility(View.java:10836)  
  9.         at android.app.Dialog.show(Dialog.java:307)  
  10.         at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)  
  11.         at android.view.View.performClick(View.java:7125)  
  12.         at android.view.View.performClickInternal(View.java:7102) 

所以在線程中更新UI的重點(diǎn)是創(chuàng)建它的ViewRootImpl和checkThread所在的線程是否一致。

如何在主線程中訪問(wèn)網(wǎng)絡(luò)

在網(wǎng)絡(luò)請(qǐng)求之前添加如下代碼 

 
 
 
 
  1. StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();  
  2. StrictMode.setThreadPolicy(policy); 

StrictMode(嚴(yán)苛模式)Android2.3引入,用于檢測(cè)兩大問(wèn)題:ThreadPolicy(線程策略)和VmPolicy(VM策略),這里把嚴(yán)苛模式的網(wǎng)絡(luò)檢測(cè)關(guān)了,就可以在主線程中執(zhí)行網(wǎng)絡(luò)操作了,一般是不建議這么做的。關(guān)于嚴(yán)苛模式可以查看這里。

系統(tǒng)為什么不建議在子線程中訪問(wèn)UI?

這是因?yàn)?Android 的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?缺點(diǎn)有兩個(gè)

  1.  首先加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜
  2.  鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。

所以最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。(安卓開(kāi)發(fā)藝術(shù)探索)

子線程如何通知主線程更新UI(都是通過(guò)Handle發(fā)送消息到主線程操作UI的)

  1.  主線程中定義 Handler,子線程通過(guò) mHandler 發(fā)送消息,主線程 Handler 的 handleMessage 更新UI。
  2.  用 Activity 對(duì)象的 runOnUiThread 方法。
  3.  創(chuàng)建 Handler,傳入 getMainLooper。
  4.  View.post(Runnable r) 。

Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死,會(huì)耗費(fèi)大量資源嗎?

從前面的主線程、子線程的分析可以看出,Looper會(huì)在線程中不斷的檢索消息,如果是子線程的Looper死循環(huán),一旦任務(wù)完成,用戶應(yīng)該手動(dòng)退出,而不是讓其一直休眠等待。(引用自Gityuan)線程其實(shí)就是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行的代碼執(zhí)行完成后,線程的生命周期便該終止了,線程退出。而對(duì)于主線程,我們是絕不希望會(huì)被運(yùn)行一段時(shí)間,自己就退出,那么如何保證能一直存活呢?簡(jiǎn)單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會(huì)被退出,例如,binder 線程也是采用死循環(huán)的方法,通過(guò)循環(huán)方式不同與 Binder 驅(qū)動(dòng)進(jìn)行讀寫(xiě)操作,當(dāng)然并非簡(jiǎn)單地死循環(huán),無(wú)消息時(shí)會(huì)休眠。Android是基于消息處理機(jī)制的,用戶的行為都在這個(gè)Looper循環(huán)中,我們?cè)谛菝邥r(shí)點(diǎn)擊屏幕,便喚醒主線程繼續(xù)進(jìn)行工作。

主線程的死循環(huán)一直運(yùn)行是不是特別消耗 CPU 資源呢?其實(shí)不然,這里就涉及到 Linux  pipe/epoll機(jī)制,簡(jiǎn)單說(shuō)就是在主線程的 MessageQueue 沒(méi)有消息時(shí),便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此時(shí)主線程會(huì)釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往 pipe 管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作。這里采用的 epoll 機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鳎举|(zhì)同步I/O,即讀寫(xiě)是阻塞的。所以說(shuō),主線程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源。

主線程的Looper何時(shí)退出

在App退出時(shí),ActivityThread中的mH(Handler)收到消息后,執(zhí)行退出。 

 
 
 
 
  1. //ActivityThread.java  
  2. case EXIT_APPLICATION:  
  3.     if (mInitialApplication != null) {  
  4.         mInitialApplication.onTerminate();  
  5.     }  
  6.     Looper.myLooper().quit();  
  7.     break; 

如果你嘗試手動(dòng)退出主線程Looper,便會(huì)拋出如下異常 

 
 
 
 
  1. Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.  
  2.     at android.os.MessageQueue.quit(MessageQueue.java:428)  
  3.     at android.os.Looper.quit(Looper.java:354)  
  4.     at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)  
  5.     at android.app.Activity.performCreate(Activity.java:7802)  
  6.     at android.app.Activity.performCreate(Activity.java:7791)  
  7.     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)  
  8.     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)  
  9.     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)   
  10.     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)   
  11.     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)   
  12.     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)   
  13.     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)   
  14.     at android.os.Handler.dispatchMessage(Handler.java:107)   
  15.     at android.os.Looper.loop(Looper.java:214)   
  16.     at android.app.ActivityThread.main(ActivityThread.java:7356)   
  17.     at java.lang.reflect.Method.invoke(Native Method)   
  18.     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)   
  19.     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 

為什么不允許退出呢,因?yàn)橹骶€程不允許退出,一旦退出就意味著程序掛了,退出也不應(yīng)該用這種方式退出。

Handler的消息處理順序

在Looper執(zhí)行消息循環(huán)loop()時(shí)會(huì)執(zhí)行下面這行代碼,msg.targe就是這個(gè)Handler對(duì)象 

 
 
 
 
  1. msg.target.dispatchMessage(msg); 

我們來(lái)看看dispatchMessage的源碼 

 
 
 
 
  1. public void dispatchMessage(@NonNull Message msg) {  
  2.      if (msg.callback != null) {  
  3.          handleCallback(msg);  
  4.      } else {  
  5.          //如果 callback 處理了該 msg 并且返回 true, 就不會(huì)再回調(diào) handleMessage  
  6.          if (mCallback != null) { 
  7.               if (mCallback.handleMessage(msg)) {  
  8.                  return;  
  9.              }  
  10.          }  
  11.          handleMessage(msg);  
  12.      }  
  13.  } 

如果Message這個(gè)對(duì)象有CallBack回調(diào)的話,這個(gè)CallBack實(shí)際上是個(gè)Runnable,就只執(zhí)行這個(gè)回調(diào),然后就結(jié)束了,創(chuàng)建該Message的CallBack代碼如下: 

 
 
 
 
  1. Message msgCallBack = Message.obtain(handler, new Runnable() {  
  2.     @Override  
  3.     public void run
    網(wǎng)站標(biāo)題:Handler的初級(jí)、中級(jí)、高級(jí)問(wèn)法,你都掌握了嗎?
    URL分享:http://m.5511xx.com/article/djchijs.html