日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

理解UI线程——SWT, Android, 和Swing的UI机理

發布時間:2023/12/9 Android 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 理解UI线程——SWT, Android, 和Swing的UI机理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

在做GUI的時候, 無論是SWT, AWT, Swing 還是Android, 都需要面對UI線程的問題, UI線程往往會被單獨的提出來單獨對待, 試著問自己,

當GUI啟動的時候, 后臺會運行幾個線程? 比如?

1. SWT 從Main函數啟動?

2. Swing 從Main函數啟動?

3. Android 界面啟動?

常常我們被告知, 主線程, UI線程, 因此這里很多會回答, 有兩個線程, 一個線程是Main, 另外一個是UI.? 如果答案是這樣, 這篇文章就是寫給你的。

?

OK, 我們以SWT為例, 設計以下方案尋找答案, 第一步, 我們看能否找到兩個線程:

1. 從Main中啟動SWT的界面, 在啟動界面前, 將Main所在的線程打印出來 這里設計為Shell中嵌入一個Button

2. 點擊Button, 運行一個耗時很長的操作, 反復修改Button的文字, 在該線程中打印該線程的名稱

?

代碼是這樣的:

[java]? view plain copy
  • public?static?void?main(String[]?args)?{??
  • ????final?Display?display?=?Display.getDefault();??
  • ????final?Shell?shell?=?new?Shell();??
  • ????shell.setSize(500,?375);??
  • ????shell.setText("SWT?Application");??
  • ????shell.setLayout(new?FillLayout());??
  • ????btn?=?new?Button(shell,?SWT.NULL);??
  • ????btn.setText("shit");??
  • ????registerAction();??
  • ????shell.open();??
  • ????shell.layout();??
  • ????while?(!shell.isDisposed())?{??
  • ????????if?(!display.readAndDispatch())??
  • ????????????display.sleep();??
  • ????}??
  • ????shell.dispose();??
  • ????display.dispose();??
  • }??
  • private?static?void?registerAction()?{??
  • ????btn.addMouseListener(new?MouseListener()?{??
  • ????????@Override??
  • ????????public?void?mouseDoubleClick(MouseEvent?e)?{??
  • ????????????//?TODO?Auto-generated?method?stub??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseDown(MouseEvent?e)?{??
  • ????????????methodA();??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseUp(MouseEvent?e)?{??
  • ????????}??
  • ????});??
  • }??
  • /**?
  • ?*?持續的跑動,?打印線程的名稱,?注意拖拽不動,?界面死掉,?直到跑完?
  • ?*/??
  • private?static?void?methodA()?{??
  • ????for?(int?i?=?0;?i?<?count;?i++)?{??
  • ????????haveArest(300);??
  • ????????System.out.println("MethodA:"?+?Thread.currentThread().getName());??
  • ????????btn.setText(i?+?"");??
  • ????}??
  • }??
  • ?

    haveArest方法在最后出現, 只是封裝了一個讓線程等待一段時間, 打印的結果都為main, 于是得到第一個重要的結論:

    UI所在的線程和Main所在的線程都是同一個線程。

    ?

    再來推斷一把:

    UI在哪個線程啟動的, 則這個線程就是UI線程.

    [java]? view plain copy
  • /**?
  • ?*?@param?args?
  • ?*/??
  • public?static?void?main(String[]?args)?{??
  • ????//?TODO?Auto-generated?method?stub??
  • ??????
  • ????Thread?t?=?new?Thread(new?Runnable()?{??
  • ????????@Override??
  • ????????public?void?run()?{??
  • ????????????createUI();??
  • ????????}??
  • ????});??
  • ????t.start();??
  • }??
  • ??
  • private?static?void?createUI()??
  • {??
  • ????System.out.println(Thread.currentThread().getName());??
  • ????final?Display?display?=?Display.getDefault();??
  • ????final?Shell?shell?=?new?Shell();??
  • ????shell.setSize(500,?375);??
  • ????shell.setText("SWT?Application");??
  • ????shell.setLayout(new?FillLayout());??
  • ????Button?btn?=?new?Button(shell,?SWT.NULL);??
  • ????btn.setText("shit");??
  • ????shell.open();??
  • ????shell.layout();??
  • ????while?(!shell.isDisposed())?{??
  • ????????if?(!display.readAndDispatch())??
  • ????????????display.sleep();??
  • ????}??
  • ????shell.dispose();??
  • ????display.dispose();??
  • }??
  • 通過打印結果發現, 推論是正確的.

    ?

    根據鋪天蓋地參考書提示, 有這樣一條定律:

    只可以存在一個UI線程

    驗證一下, 我們的驗證方式是創建兩個UI線程:

    [java]? view plain copy
  • /**?
  • ?*?@param?args?
  • ?*/??
  • public?static?void?main(String[]?args)?{??
  • ????//?TODO?Auto-generated?method?stub??
  • ??????
  • ????Thread?t?=?new?Thread(new?Runnable()?{??
  • ????????@Override??
  • ????????public?void?run()?{??
  • ????????????createUI();??
  • ????????}??
  • ????});??
  • ????t.start();??
  • ??????
  • ????t?=?new?Thread(new?Runnable()?{??
  • ????????@Override??
  • ????????public?void?run()?{??
  • ????????????createUI();??
  • ????????}??
  • ????});??
  • ????t.start();??
  • ??????
  • ??????
  • }??
  • ??
  • private?static?void?createUI()??
  • {??
  • ????System.out.println(Thread.currentThread().getName());??
  • ????final?Display?display?=?new?Display();??
  • ????final?Shell?shell?=?new?Shell();??
  • ????shell.setSize(500,?375);??
  • ????shell.setText("SWT?Application");??
  • ????shell.setLayout(new?FillLayout());??
  • ????Button?btn?=?new?Button(shell,?SWT.NULL);??
  • ????btn.setText("shit");??
  • ????shell.open();??
  • ????shell.layout();??
  • ????while?(!shell.isDisposed())?{??
  • ????????if?(!display.readAndDispatch())??
  • ????????????display.sleep();??
  • ????}??
  • ????shell.dispose();??
  • ????display.dispose();??
  • }??
  • 但這里確實創建了兩個線程。看來一個進程是可以創建兩個線程的。

    ?可以存在一個或者多個UI線程,?下次看到參考書這么寫的時候, 可以BS它了。??

    ?

    之前犯了一個錯誤就是用Diplay display = Display.getDefault(); 這樣得到的是前一個線程創建的Display,故不能創建. 造成只能創建一個UI線程的錯覺

    ?

    當然我們的研究不能到此為止, 我們需要探究一下, 為什么總是被告知更新UI的動作要放在UI線程中?

    回到第一個例子中, 即:


    ?

    [java]? view plain copy
  • public?static?void?main(String[]?args)?{??
  • ????final?Display?display?=?Display.getDefault();??
  • ????final?Shell?shell?=?new?Shell();??
  • ????shell.setSize(500,?375);??
  • ????shell.setText("SWT?Application");??
  • ????shell.setLayout(new?FillLayout());??
  • ????btn?=?new?Button(shell,?SWT.NULL);??
  • ????btn.setText("shit");??
  • ????registerAction();??
  • ????shell.open();??
  • ????shell.layout();??
  • ????while?(!shell.isDisposed())?{??
  • ????????if?(!display.readAndDispatch())??
  • ????????????display.sleep();??
  • ????}??
  • ????shell.dispose();??
  • ????display.dispose();??
  • }??
  • private?static?void?registerAction()?{??
  • ????btn.addMouseListener(new?MouseListener()?{??
  • ????????@Override??
  • ????????public?void?mouseDoubleClick(MouseEvent?e)?{??
  • ????????????//?TODO?Auto-generated?method?stub??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseDown(MouseEvent?e)?{??
  • ????????????methodA();??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseUp(MouseEvent?e)?{??
  • ????????}??
  • ????});??
  • }??
  • /**?
  • ?*?持續的跑動,?打印線程的名稱,?注意拖拽不動,?界面死掉,?直到跑完?
  • ?*/??
  • private?static?void?methodA()?{??
  • ????for?(int?i?=?0;?i?<?count;?i++)?{??
  • ????????haveArest(300);??
  • ????????System.out.println("MethodA:"?+?Thread.currentThread().getName());??
  • ????????btn.setText(i?+?"");??
  • ????}??
  • }??
  • ?

    運行的時候拖動試試, 發現不動, 直到for循環中修改btn的操作完成.

    這里我們不難明白一個觀點:

    同一個線程的情況下, 一個操作(拖動), 是需要等待另外一個操作(更新btn)完成后, 才可以進行的。

    不難理解, 我們常用的做法是:

    通過啟動另外一個線程, 在cpu微小的間隔時間內,完成兩個動作的交替

    于是有了下面的代碼:

    [java]?view plaincopy
  • private?static?Button?btn;??
  • ??
  • private?static?final?int?count?=?20;??
  • public?static?void?main(String[]?args)?{??
  • ????final?Display?display?=?Display.getDefault();??
  • ????final?Shell?shell?=?new?Shell();??
  • ????shell.setSize(500,?375);??
  • ????shell.setText("SWT?Application");??
  • ????shell.setLayout(new?FillLayout());??
  • ????btn?=?new?Button(shell,?SWT.NULL);??
  • ????btn.setText("shit");??
  • ????registerAction();??
  • ????shell.open();??
  • ????shell.layout();??
  • ????while?(!shell.isDisposed())?{??
  • ????????if?(!display.readAndDispatch())??
  • ????????????display.sleep();??
  • ????}??
  • ????shell.dispose();??
  • ????display.dispose();??
  • }??
  • private?static?void?registerAction()?{??
  • ????btn.addMouseListener(new?MouseListener()?{??
  • ????????@Override??
  • ????????public?void?mouseDoubleClick(MouseEvent?e)?{??
  • ????????????//?TODO?Auto-generated?method?stub??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseDown(MouseEvent?e)?{??
  • ????????????methodB();??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseUp(MouseEvent?e)?{??
  • ????????}??
  • ????});??
  • }??
  • /**?
  • ?*?為了解決拖拽不動,?界面死掉,?增加線程控制,?但產生了Invalid?thread?access的問題?
  • ?*/??
  • private?static?void?methodB()?{??
  • ????Thread?t?=?new?Thread(new?Runnable()?{??
  • ????????@Override??
  • ????????public?void?run()?{??
  • ????????????for?(int?i?=?0;?i?<?count;?i++)?{??
  • ????????????????haveArest(300);??
  • ????????????????System.out.println("MethodB:"??
  • ????????????????????????+?Thread.currentThread().getName());??
  • ????????????????btn.setText(i?+?"");??
  • ????????????}??
  • ????????}??
  • ????});??
  • ????t.start();??
  • }??

  • ?

    但這樣發現會報錯, 原因是, 線程訪問出錯了, 因為有一個這樣的規則需要我們保障:

    所有的UI相關的操作, 務必保證在UI線程中更新.

    為什么會有這樣一條鐵律? 原因是界面的消息需要分發到各大控件上面去, 如果不能保證UI在相同的線程, 分發起來就會比較復雜. UI本身占用的資源比較多.? 如果在將UI分屬不同的線程, 切換起來, 將耗費大量的CPU資源.

    ?

    為了保證這條, SWT 是這么做的, 利用Diplay這個變量獲取UI線程, 然后在其中做UI訪問和操作:

    [java]? view plain copy
  • private?static?Button?btn;??
  • ??
  • private?static?final?int?count?=?20;??
  • public?static?void?main(String[]?args)?{??
  • ????final?Display?display?=?Display.getDefault();??
  • ????final?Shell?shell?=?new?Shell();??
  • ????shell.setSize(500,?375);??
  • ????shell.setText("SWT?Application");??
  • ????shell.setLayout(new?FillLayout());??
  • ????btn?=?new?Button(shell,?SWT.NULL);??
  • ????btn.setText("shit");??
  • ????registerAction();??
  • ????shell.open();??
  • ????shell.layout();??
  • ????while?(!shell.isDisposed())?{??
  • ????????if?(!display.readAndDispatch())??
  • ????????????display.sleep();??
  • ????}??
  • ????shell.dispose();??
  • ????display.dispose();??
  • }??
  • private?static?void?registerAction()?{??
  • ????btn.addMouseListener(new?MouseListener()?{??
  • ????????@Override??
  • ????????public?void?mouseDoubleClick(MouseEvent?e)?{??
  • ????????????//?TODO?Auto-generated?method?stub??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseDown(MouseEvent?e)?{??
  • ????????????methodC();??
  • ????????}??
  • ????????@Override??
  • ????????public?void?mouseUp(MouseEvent?e)?{??
  • ????????}??
  • ????});??
  • }??
  • ??
  • private?static?void?methodC()?{??
  • ????Thread?t?=?new?Thread(new?Runnable()?{??
  • ????????@Override??
  • ????????public?void?run()?{??
  • ????????????for?(int?i?=?0;?i?<?count;?i++)?{??
  • ????????????????System.out.println("MethodB?Thread:"??
  • ????????????????????????+?Thread.currentThread().getName());??
  • ??????????????
  • ????????????????haveArest(300);??
  • ????????????????final?Display?display?=?Display.getDefault();??
  • ????????????????final?String?s?=?i?+?"";??
  • ????????????????if?((display?!=?null)?&&?(!display.isDisposed()))?{??
  • ????????????????????display.asyncExec(new?Runnable()?{??
  • ????????????????????????@Override??
  • ????????????????????????public?void?run()?{??
  • ????????????????????????????System.out.println("MethodB?Thread?asyncExec:"??
  • ????????????????????????????????????+?Thread.currentThread().getName());??
  • ????????????????????????????btn.setText(s);??
  • ????????????????????????}??
  • ????????????????????});??
  • ????????????????}??
  • ????????????}??
  • ????????}??
  • ????});??
  • ????t.start();??
  • }??
  • ??
  • private?static?void?haveArest(int?sleepTime)??
  • {??
  • ????try?{??
  • ????????Thread.sleep(sleepTime);??
  • ????}?catch?(InterruptedException?e)?{??
  • ????????//?TODO?Auto-generated?catch?block??
  • ????????e.printStackTrace();??
  • ????}??
  • }??
  • ?

    后面會繼續關注Swing和Android的例子, 相信這些也是大同小異的.?關鍵是, UI特殊, 但特殊性不在于它是一個額外的線程.

    ?

    這樣的應用其實很多, 比如我們不斷刷表格的時候,?為了讓界面能接受其它的響應事件, 一般都把刷表格的動作放置到另外的線程中, 用Display.asychronize()來保障其訪問UI元素的安全行(即在UI中訪問).?

    ?

    總結一下, 本文由如下結論:?
    UI線程和主線程,普通線程的關系?
    1. UI線程和Main線程沒有必然聯系, 從Main函數啟動, 也可以從一個其它的線程啟動. 啟動UI的線程, 則為UI線程?
    2. 如果第一個線程啟動了UI. 則第一個線程則成為UI線程. 如果第二個線程涉及UI操作, 則需要保證這個操作放在UI線程中. 否則會出現Invalid thread access錯誤.?

    SWT為什么會有Display.asyncExec(new Runnable())操作:?
    1. 當界面執行了長時段的UI操作, 比如進度條, 此時如果把更新UI的操作放在唯一的UI線程中執行, 那么本線程將全部消耗CPU資源, 造成界面無法拖動.拖動則界面死掉MethodA()。?
    2. 為了解決問題1, 我們一般另外啟動一個線程進行操作, 這樣使得界面可以拖動, 但是UI的操作無法在其它的線程中完成, 只能在UI線程中完成,?
    3. Display.asyncExec(new Runnable()的目的就是將這個動作放在UI線程中完成. 這樣避免報錯Invalid thread access

    ?

    ?

    補充SWT的知識, 很多不明白Display.asyncExec 和Display.syncExec的區別, 用個例子說明一下:

    [java]? view plain copy
  • /**?
  • ?*?在UI線程中跑動,?注意,?在UI線程中跑動asyncExec/syncExec都不能解決拖動的問題,?只能另起線程?
  • ?*?才能解決如:methodC?
  • ?*/??
  • private?static?void?methodD()?{??
  • ????for?(int?i?=?0;?i?<?count;?i++)?{??
  • ????????haveArest(300);??
  • ????????final?Display?display?=?Display.getDefault();??
  • ????????final?String?s?=?i?+?"";??
  • ????????if?((display?!=?null)?&&?(!display.isDisposed()))?{??
  • ????????????display.syncExec(new?Runnable()?{??
  • ????????????????@Override??
  • ????????????????public?void?run()?{??
  • ????????????????????//如果是asyncExec的話,?這里到最后次才執行??
  • ????????????????????//?asyncExec要等到發起asyncExec的線程執行完畢,?他才有機會執行。在單線程的情況下,?發起asyncExec的線程和asyncExec里面的run內容都在同一個線程??
  • ????????????????????//所以要等到asyncExec執行完畢,?asyncExec中run的東西,?才有機會執行??
  • ????????????????????//syncExec則不同,?它務必要保證里面的方法執行后,再回到發起syncExec方法所在的線程,?所以這里相當于一個流暢的串行操作??
  • ????????????????????btn.setText(s);??
  • ????????????????????System.out.println(""?+?s);??
  • ????????????????}??
  • ????????????});??
  • ????????}??
  • ????}??
  • }??
  • ?

    按注釋運行下, 就會發現這里面大有玄機, 不過我這邊并不是為了解決SWT的問題, 而是針對所有的UI線程來的, 所以, 不再做解釋.

    ?

    推薦閱讀.?該牛人的長篇大作.

    ?

    多謝同事章導對SWT修正的問題. 即使彌補了誤導大家的觀點, :)

    ?

    ?

    ?

    Android 也是相同的原理:

    ?

    1. 通過多線程避免界面假死


    2. 通過Hander保證訪問界面元素在UI線程中進行.

    ?

    其它的一些細微差別, 不需要多講. 原理乃一個模子出來的.

    ?

    一個Android Helloworld運行起來的時候, 有四個線程

    ?

    1. 傳說中的Main線程

    ?

    2. 另外三個都是Binder Thread, 貌似是為了跨進程通信用的監聽線程.

    ?

    貌似很多Android的教程都把UI線程當特殊的一個線程.

    ?

    轉載于:https://my.oschina.net/mojiewhy/blog/180525

    總結

    以上是生活随笔為你收集整理的理解UI线程——SWT, Android, 和Swing的UI机理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。