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

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

在做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
  1. public static void main(String[] args) {  
  2.     final Display display = Display.getDefault();  
  3.     final Shell shell = new Shell();  
  4.     shell.setSize(500, 375);  
  5.     shell.setText("SWT Application");  
  6.     shell.setLayout(new FillLayout());  
  7.     btn = new Button(shell, SWT.NULL);  
  8.     btn.setText("shit");  
  9.     registerAction();  
  10.     shell.open();  
  11.     shell.layout();  
  12.     while (!shell.isDisposed()) {  
  13.         if (!display.readAndDispatch())  
  14.             display.sleep();  
  15.     }  
  16.     shell.dispose();  
  17.     display.dispose();  
  18. }  
  19. private static void registerAction() {  
  20.     btn.addMouseListener(new MouseListener() {  
  21.         @Override  
  22.         public void mouseDoubleClick(MouseEvent e) {  
  23.             // TODO Auto-generated method stub  
  24.         }  
  25.         @Override  
  26.         public void mouseDown(MouseEvent e) {  
  27.             methodA();  
  28.         }  
  29.         @Override  
  30.         public void mouseUp(MouseEvent e) {  
  31.         }  
  32.     });  
  33. }  
  34. /** 
  35.  * 持续的跑动, 打印线程的名称, 注意拖拽不动, 界面死掉, 直到跑完 
  36.  */  
  37. private static void methodA() {  
  38.     for (int i = 0; i < count; i++) {  
  39.         haveArest(300);  
  40.         System.out.println("MethodA:" + Thread.currentThread().getName());  
  41.         btn.setText(i + "");  
  42.     }  
  43. }  

 

haveArest方法在最后出现, 只是封装了一个让线程等待一段时间, 打印的结果都为main, 于是得到第一个重要的结论:

UI所在的线程和Main所在的线程都是同一个线程。

 

再来推断一把:

UI在哪个线程启动的, 则这个线程就是UI线程.

[java]  view plain copy
  1. /** 
  2.  * @param args 
  3.  */  
  4. public static void main(String[] args) {  
  5.     // TODO Auto-generated method stub  
  6.       
  7.     Thread t = new Thread(new Runnable() {  
  8.         @Override  
  9.         public void run() {  
  10.             createUI();  
  11.         }  
  12.     });  
  13.     t.start();  
  14. }  
  15.   
  16. private static void createUI()  
  17. {  
  18.     System.out.println(Thread.currentThread().getName());  
  19.     final Display display = Display.getDefault();  
  20.     final Shell shell = new Shell();  
  21.     shell.setSize(500, 375);  
  22.     shell.setText("SWT Application");  
  23.     shell.setLayout(new FillLayout());  
  24.     Button btn = new Button(shell, SWT.NULL);  
  25.     btn.setText("shit");  
  26.     shell.open();  
  27.     shell.layout();  
  28.     while (!shell.isDisposed()) {  
  29.         if (!display.readAndDispatch())  
  30.             display.sleep();  
  31.     }  
  32.     shell.dispose();  
  33.     display.dispose();  
  34. }  

通过打印结果发现, 推论是正确的.

 

根据铺天盖地参考书提示, 有这样一条定律:

只可以存在一个UI线程

验证一下, 我们的验证方式是创建两个UI线程:

[java]  view plain copy
  1. /** 
  2.  * @param args 
  3.  */  
  4. public static void main(String[] args) {  
  5.     // TODO Auto-generated method stub  
  6.       
  7.     Thread t = new Thread(new Runnable() {  
  8.         @Override  
  9.         public void run() {  
  10.             createUI();  
  11.         }  
  12.     });  
  13.     t.start();  
  14.       
  15.     t = new Thread(new Runnable() {  
  16.         @Override  
  17.         public void run() {  
  18.             createUI();  
  19.         }  
  20.     });  
  21.     t.start();  
  22.       
  23.       
  24. }  
  25.   
  26. private static void createUI()  
  27. {  
  28.     System.out.println(Thread.currentThread().getName());  
  29.     final Display display = new Display();  
  30.     final Shell shell = new Shell();  
  31.     shell.setSize(500, 375);  
  32.     shell.setText("SWT Application");  
  33.     shell.setLayout(new FillLayout());  
  34.     Button btn = new Button(shell, SWT.NULL);  
  35.     btn.setText("shit");  
  36.     shell.open();  
  37.     shell.layout();  
  38.     while (!shell.isDisposed()) {  
  39.         if (!display.readAndDispatch())  
  40.             display.sleep();  
  41.     }  
  42.     shell.dispose();  
  43.     display.dispose();  
  44. }  

但这里确实创建了两个线程。看来一个进程是可以创建两个线程的。

 可以存在一个或者多个UI线程, 下次看到参考书这么写的时候, 可以BS它了。  

 

之前犯了一个错误就是用Diplay display = Display.getDefault(); 这样得到的是前一个线程创建的Display,故不能创建. 造成只能创建一个UI线程的错觉

 

当然我们的研究不能到此为止, 我们需要探究一下, 为什么总是被告知更新UI的动作要放在UI线程中?

回到第一个例子中, 即:


 

[java]  view plain copy
  1. public static void main(String[] args) {  
  2.     final Display display = Display.getDefault();  
  3.     final Shell shell = new Shell();  
  4.     shell.setSize(500, 375);  
  5.     shell.setText("SWT Application");  
  6.     shell.setLayout(new FillLayout());  
  7.     btn = new Button(shell, SWT.NULL);  
  8.     btn.setText("shit");  
  9.     registerAction();  
  10.     shell.open();  
  11.     shell.layout();  
  12.     while (!shell.isDisposed()) {  
  13.         if (!display.readAndDispatch())  
  14.             display.sleep();  
  15.     }  
  16.     shell.dispose();  
  17.     display.dispose();  
  18. }  
  19. private static void registerAction() {  
  20.     btn.addMouseListener(new MouseListener() {  
  21.         @Override  
  22.         public void mouseDoubleClick(MouseEvent e) {  
  23.             // TODO Auto-generated method stub  
  24.         }  
  25.         @Override  
  26.         public void mouseDown(MouseEvent e) {  
  27.             methodA();  
  28.         }  
  29.         @Override  
  30.         public void mouseUp(MouseEvent e) {  
  31.         }  
  32.     });  
  33. }  
  34. /** 
  35.  * 持续的跑动, 打印线程的名称, 注意拖拽不动, 界面死掉, 直到跑完 
  36.  */  
  37. private static void methodA() {  
  38.     for (int i = 0; i < count; i++) {  
  39.         haveArest(300);  
  40.         System.out.println("MethodA:" + Thread.currentThread().getName());  
  41.         btn.setText(i + "");  
  42.     }  
  43. }  

 

运行的时候拖动试试, 发现不动, 直到for循环中修改btn的操作完成.

这里我们不难明白一个观点:

同一个线程的情况下, 一个操作(拖动), 是需要等待另外一个操作(更新btn)完成后, 才可以进行的。

不难理解, 我们常用的做法是:

通过启动另外一个线程, 在cpu微小的间隔时间内,完成两个动作的交替

于是有了下面的代码:

[java] view plaincopy
  1. private static Button btn;  
  2.   
  3. private static final int count = 20;  
  4. public static void main(String[] args) {  
  5.     final Display display = Display.getDefault();  
  6.     final Shell shell = new Shell();  
  7.     shell.setSize(500, 375);  
  8.     shell.setText("SWT Application");  
  9.     shell.setLayout(new FillLayout());  
  10.     btn = new Button(shell, SWT.NULL);  
  11.     btn.setText("shit");  
  12.     registerAction();  
  13.     shell.open();  
  14.     shell.layout();  
  15.     while (!shell.isDisposed()) {  
  16.         if (!display.readAndDispatch())  
  17.             display.sleep();  
  18.     }  
  19.     shell.dispose();  
  20.     display.dispose();  
  21. }  
  22. private static void registerAction() {  
  23.     btn.addMouseListener(new MouseListener() {  
  24.         @Override  
  25.         public void mouseDoubleClick(MouseEvent e) {  
  26.             // TODO Auto-generated method stub  
  27.         }  
  28.         @Override  
  29.         public void mouseDown(MouseEvent e) {  
  30.             methodB();  
  31.         }  
  32.         @Override  
  33.         public void mouseUp(MouseEvent e) {  
  34.         }  
  35.     });  
  36. }  
  37. /** 
  38.  * 为了解决拖拽不动, 界面死掉, 增加线程控制, 但产生了Invalid thread access的问题 
  39.  */  
  40. private static void methodB() {  
  41.     Thread t = new Thread(new Runnable() {  
  42.         @Override  
  43.         public void run() {  
  44.             for (int i = 0; i < count; i++) {  
  45.                 haveArest(300);  
  46.                 System.out.println("MethodB:"  
  47.                         + Thread.currentThread().getName());  
  48.                 btn.setText(i + "");  
  49.             }  
  50.         }  
  51.     });  
  52.     t.start();  
  53. }  

 

但这样发现会报错, 原因是, 线程访问出错了, 因为有一个这样的规则需要我们保障:

所有的UI相关的操作, 务必保证在UI线程中更新.

为什么会有这样一条铁律? 原因是界面的消息需要分发到各大控件上面去, 如果不能保证UI在相同的线程, 分发起来就会比较复杂. UI本身占用的资源比较多.  如果在将UI分属不同的线程, 切换起来, 将耗费大量的CPU资源.

 

为了保证这条, SWT 是这么做的, 利用Diplay这个变量获取UI线程, 然后在其中做UI访问和操作:

[java]  view plain copy
  1. private static Button btn;  
  2.   
  3. private static final int count = 20;  
  4. public static void main(String[] args) {  
  5.     final Display display = Display.getDefault();  
  6.     final Shell shell = new Shell();  
  7.     shell.setSize(500, 375);  
  8.     shell.setText("SWT Application");  
  9.     shell.setLayout(new FillLayout());  
  10.     btn = new Button(shell, SWT.NULL);  
  11.     btn.setText("shit");  
  12.     registerAction();  
  13.     shell.open();  
  14.     shell.layout();  
  15.     while (!shell.isDisposed()) {  
  16.         if (!display.readAndDispatch())  
  17.             display.sleep();  
  18.     }  
  19.     shell.dispose();  
  20.     display.dispose();  
  21. }  
  22. private static void registerAction() {  
  23.     btn.addMouseListener(new MouseListener() {  
  24.         @Override  
  25.         public void mouseDoubleClick(MouseEvent e) {  
  26.             // TODO Auto-generated method stub  
  27.         }  
  28.         @Override  
  29.         public void mouseDown(MouseEvent e) {  
  30.             methodC();  
  31.         }  
  32.         @Override  
  33.         public void mouseUp(MouseEvent e) {  
  34.         }  
  35.     });  
  36. }  
  37.   
  38. private static void methodC() {  
  39.     Thread t = new Thread(new Runnable() {  
  40.         @Override  
  41.         public void run() {  
  42.             for (int i = 0; i < count; i++) {  
  43.                 System.out.println("MethodB Thread:"  
  44.                         + Thread.currentThread().getName());  
  45.               
  46.                 haveArest(300);  
  47.                 final Display display = Display.getDefault();  
  48.                 final String s = i + "";  
  49.                 if ((display != null) && (!display.isDisposed())) {  
  50.                     display.asyncExec(new Runnable() {  
  51.                         @Override  
  52.                         public void run() {  
  53.                             System.out.println("MethodB Thread asyncExec:"  
  54.                                     + Thread.currentThread().getName());  
  55.                             btn.setText(s);  
  56.                         }  
  57.                     });  
  58.                 }  
  59.             }  
  60.         }  
  61.     });  
  62.     t.start();  
  63. }  
  64.   
  65. private static void haveArest(int sleepTime)  
  66. {  
  67.     try {  
  68.         Thread.sleep(sleepTime);  
  69.     } catch (InterruptedException e) {  
  70.         // TODO Auto-generated catch block  
  71.         e.printStackTrace();  
  72.     }  
  73. }  

 

后面会继续关注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
  1. /** 
  2.  * 在UI线程中跑动, 注意, 在UI线程中跑动asyncExec/syncExec都不能解决拖动的问题, 只能另起线程 
  3.  * 才能解决如:methodC 
  4.  */  
  5. private static void methodD() {  
  6.     for (int i = 0; i < count; i++) {  
  7.         haveArest(300);  
  8.         final Display display = Display.getDefault();  
  9.         final String s = i + "";  
  10.         if ((display != null) && (!display.isDisposed())) {  
  11.             display.syncExec(new Runnable() {  
  12.                 @Override  
  13.                 public void run() {  
  14.                     //如果是asyncExec的话, 这里到最后次才执行  
  15.                     // asyncExec要等到发起asyncExec的线程执行完毕, 他才有机会执行。在单线程的情况下, 发起asyncExec的线程和asyncExec里面的run内容都在同一个线程  
  16.                     //所以要等到asyncExec执行完毕, asyncExec中run的东西, 才有机会执行  
  17.                     //syncExec则不同, 它务必要保证里面的方法执行后,再回到发起syncExec方法所在的线程, 所以这里相当于一个流畅的串行操作  
  18.                     btn.setText(s);  
  19.                     System.out.println("" + s);  
  20.                 }  
  21.             });  
  22.         }  
  23.     }  
  24. }  

 

按注释运行下, 就会发现这里面大有玄机, 不过我这边并不是为了解决SWT的问题, 而是针对所有的UI线程来的, 所以, 不再做解释.

 

推荐阅读. 该牛人的长篇大作.

 

多谢同事章导对SWT修正的问题. 即使弥补了误导大家的观点, :)

 

 

 

Android 也是相同的原理:

 

1. 通过多线程避免界面假死


2. 通过Hander保证访问界面元素在UI线程中进行.

 

其它的一些细微差别, 不需要多讲. 原理乃一个模子出来的.

 

一个Android Helloworld运行起来的时候, 有四个线程

 

1. 传说中的Main线程

 

2. 另外三个都是Binder Thread, 貌似是为了跨进程通信用的监听线程.

 

貌似很多Android的教程都把UI线程当特殊的一个线程.

 

转载于:https://my.oschina.net/mojiewhy/blog/180525

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292383.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python自动填写小程序表单_新年好!教大家用Python写一个自动回复拜年信息的小程序!...

原标题&#xff1a;新年好&#xff01;教大家用Python写一个自动回复拜年信息的小程序&#xff01;过年期间&#xff0c;想必大家都收到很多拜年信息吧&#xff01;有没有也被拜年短信(大部分是群发)搞得很焦虑&#xff1f;不回复似乎显得很没有礼貌&#xff0c;一一回复又累心…

C#多线程开发-并发集合中的ConcurrentQueue

前言大家好&#xff0c;我是阿辉。上一篇博文简单介绍了C#中支持并发的数据字典&#xff0c;简单举例说明比较了常规集合与ConcurrentDictionary的读写速度。下来简单介绍其中一个线程安全队列ConcurrentQueue;ConcurrentQueue队列我们不陌生&#xff0c;在数据结构这门课中就有…

HDU 5141

这个题 LIS 并查集的思想 链式前向星 要求找s(i,j)使i j 能有最长的LIS 。。。 做法是枚举每一个j 即终点 算 起点 的可能 无力吐槽了 bc 的时候写错了一个地方 导致TLE 后来幡然醒悟了 改了就a了 不想说什么了 直接上代码 #include <cstdio> #include <…

MySQL存储过程相互调用

什么都不说了上代码&#xff1a; 方式一&#xff1a; 第一个存储过程&#xff1a;test1,参数如下&#xff1a;IN user_name VARCHAR(50),OUT uid bigint(20) BEGIN#Routine body goes here...DECLARE u_id BIGINT(20) DEFAULT 11;SELECT user_id INTO uid FROM tbl_useralias …

一个人动情之后的表现......

1 卖家能有什么坏心思呢&#xff08;via.城与橙与澄&#xff0c;侵删&#xff09;▼2 严重怀疑传了答案▼3 别说我还真没留意到&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 领导说“辛苦了”&#xff0c;你要怎么回答▼5 哦吼&#xff08;素材来源网络&#…

Android之推荐看的Android源码

推荐阅读的源码 AOSP项目这么庞大,就算是Framework部分也有够看上一阵子的,所以推荐从常用的看起,由浅及深,同时向横向和纵向深入阅读。 开始 Handler-Message-Looper Handler被称为“异步提交器”,是Android开发入门教程必定谈及的东西,这也是Activity等组件的工作机…

线性代数第五版吉尔伯特课后答_线性代数同济第五版第六章课后习题答案!

搜集 | 整理 | 测试 | 小愉免责声明&#xff1a;以下资源或软件均来自互联网&#xff0c;仅供学习和交流使用&#xff0c;如有侵权请联系删除&#xff0c;请勿用于商业和非法途径等&#xff0c;如有法律纠纷与本人无关&#xff01;本文未经允许&#xff0c;不得转载&#xff0…

2021,我的输入输出

前言2021年&#xff0c;我到底进行了哪些输入&#xff0c;又是如何输出的&#xff0c;借由这篇文章回顾一下。新技术.NET技术日新月异&#xff0c;今年我也尝试学习了其中一部分。BlazorBlazor允许我们使用C#而不是JavaScript构建交互式客户端Web应用程序&#xff0c;对于后端出…

iOS笔记之UIKit_UINavigationController

//设置导航条的样式 self.navigationController.navigationBar.barStyle UIBarStyleBlackTranslucent; //默认是白色 Bar 字体颜色黑色&#xff0c;如果样式设置黑色&#xff0c;对应的字体就是白色。 //定义导航条的时候使用 self.navigationController.navigationBar.trans…

字符串之找到字符串的最大无重复字符串子串

题目: 字符串之找到字符串的最大无重复字符子串 举例: str = "adcd" return 4 str = "aabcd" I know this str is adc so return 3 要求: 时间复杂度为O(N) 代码实现: package com.chenyu.string.cn;public class MaxUnique {public static v…

hive日期函数

今天select from_unixtime(unix_timestamp(),yyyy-MM-dd HH:mm:ss) UNIX时间戳转日期函数: from_unixtime 语法: from_unixtime(bigint unixtime[, string format]) 返回值: string 说明: 转化UNIX时间戳&#xff08;从1970-01-01 00:00:00 UTC到指定时间的秒数&#xff09;到…

sql长整型_SQL 性能优化梳理

先简单梳理下Mysql的基本概念&#xff0c;然后分创建时和查询时这两个阶段的优化展开。1 基本概念简述1.1 逻辑架构第一层&#xff1a;客户端通过连接服务&#xff0c;将要执行的sql指令传输过来第二层&#xff1a;服务器解析并优化sql&#xff0c;生成最终的执行计划并执行第三…

网络的东西南北

前一陣子連續出差, 加上許多的內部會議, 搞的差點想去撞牆把自己搞昏之後就可以休息一下. 但是家中還有嗷嗷待哺的嬰兒需要爸爸幫他洗屁屁, 所以只有咬牙繼續撐下去. 不過這兩個月來, 不過在公司內部還是外部, 我都收到一樣類似的老問題那就是&#xff1a;&#xff08;認識我的…

K8s 中使用 cert-manager 申请免费 Https 证书

K8s 中使用 cert-manager 申请免费 Https 证书Intro最近在尝试将自己的应用从自己用 kind 部署的一个 k8s 集群迁移到 Azure 的 AKS 上&#xff0c;其中一个问题就是 https 证书&#xff0c;原来的 k8s 集群是放在 nginx 后端的并没有直接管理 https 证书&#xff0c;https 证书…

为什么要学数学?因为它真的没用啊!

全世界只有3.14 % 的人关注了爆炸吧知识数学之用无用之用有一天&#xff0c;表妹过来问了我两个问题&#xff1a;数学有什么用&#xff1f;那些深奥的公式对于普通人有什么意义&#xff1f;相信大多数人都有这个疑问&#xff0c;但总是找不到一个标准答案。问老师&#xff0c;他…

UI设计教程-界面设计构图

九宫格构图&#xff0c;圆心点放射形构图&#xff0c;三角形构图&#xff0c;SF字形构图。 1.九宫格网格构图 这种版式主要运用在分类为主的一级页面&#xff0c;起到功能分类的作用。 通常在界面设计中&#xff0c;我们会利用网格在界面进行布局&#xff0c;根据水平方向和垂直…

Android之最好理解的Binder机制

转载&#xff1a;http://weishu.me/2016/01/12/binder-index-for-newer/ Binder学习指南 发表于 2016-01-12 | 92条评论 | 34011次阅读毫不夸张地说&#xff0c;Binder是Android系统中最重要的特性之一&#xff1b;正如其名“粘合剂”所喻&#xff0c;它是系统间各个组件…

gridview实现分页

前台代码&#xff1a; <asp:GridView ID"GridView1" runat"server" AllowPaging"True" AutoGenerateColumns"False" DataKeyNames"ScoreID" DataSourceID"SqlDataSource1" onpageinde…

sql年月日24小时制_24小时制的「无码」真人秀,令人叫绝

几年前&#xff0c;我们总说互联网给世界带来了巨大的变化。到了现在&#xff0c;没想到自媒体的发展居然也能改变生活。但大家知道吗&#xff0c;其实早在20年前&#xff0c;美国那边就曾因“直播”掀起过不小的风浪——还是尺度挺大的那种。以至于毒师看过以后&#xff0c;整…