java的gui(一)

[color=red]注:该文章只是一个尝试。
一直在想办法以一种良好的方式省时省事地记录自己的思维,系统地管理学到的知识,试过不少方法,都不凑效
本文将试着从开始着手看一个问题开始,每一步都跟踪自己的思维,记录相关的资料的出处[/color]



因为工作中会经常用到java的gui,一直在找一个契机,去学习一下java的gui相关知识。

在看到《java concurrency in practice》的第九章第一节:Why are GUIs Single-threaded?时,这个等待了许久的契机终于出现了。


这里先简介一下该章第一节的内容(根据自己的理解,着重点可能与该章节不相同):
[b]1.单线程的gui框架并不是唯一的,除了java,像Qt, NextStep, MacOS Cocoa, X Windows都是用单线程的[/b]

[b]2.不缺乏对多线程gui框架的尝试,但基本都以失败告终了,为什么不能够用多线程?[/b]
原因有两个:
a.两类不同形式的运行动作,让多线程的gui天生就趋向于死锁:一类是自上向下的,如改变字体颜色,这个命令会从应用层一直往下走,一直到操作系统,然后执行渲染工作,一类是自下向上的,如鼠标的点击,从用户点击,到操作系统封装的事件,一直往上走,直到应用程序,根据死锁产生的原因之一:两种相反方向的加锁(可参考《java concurrency in practice》第十章),这两种gui所不可能避免的动作必然会导致死锁
b.mvc模式,我们知道,c可以改变m,然后反应到v上去,c也可以调用v的接口,让v去对m进行查询,这其实也是反向加锁的两种动作

[b]3.简述了EDT的一些缺点(长任务)和限制(更新gui的操作应该都由EDT来执行)[/b]


其中,为什么不能够用多线程的两个原因中第一个原因,文章提供了一篇拓展阅读,但文中的地址有点问题,正确的地址应该是:[url="http://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html"]Multithreaded toolkits: A failed dream?[/url]
懒得看英文的朋友可以下载附件(虽然翻译水平差了点,但应该意思是到位了)


文章其实也没说太多内容,主要还是针对于上述原因1做一些详细的论述,但也根据该文章了解到了这样一些内容
[b]a.awt初衷是设计多线程的,但最后发现无法兑现承诺,之后做的swing是单线程的。[/b]
[b]b.不能用多线程的根源性问题在于上述原因1,即使这些工具包的开发者很聪明,很仔细,也是无法避免的[/b]
[b]c."failed dreams"的含义:一个梦想,虽然事实上是无法达到的(人们其实不会都这样认为),但是不断地有人,很多的人去为这个梦想去付出努力,让这样一个梦想无限地趋向于实现(当时查了之后有点热血沸腾的感觉)[/b]
[b]d.多线程的gui不一定是不可实现的,但他需要开发者足够地有天份,了解工具包的细节,了解每一个并发的过程,但作为一个用于商业的组件,显然是不现实的。[/b]


看完了文章,也基本得出了接下来的学习的一个方向:深入awt和swing工具包(因为根据上述文章的描述,awt起初是设计为多线程的,同时至今它也是线程安全的,而swing是参考了awt的设计做的,用的是标准的单线程的事件队列)

于是,基于了解java gui框架的目的,在网上经过简单搜索后发现这么两篇文章:
[url="http://www.ibm.com/developerworks/cn/java/j-j2int/"]java 2用户界面[/url]
[url="http://www.ibm.com/developerworks/cn/opensource/os-swingswt/"]SWT、Swing 或 AWT:哪个更适合您?[/url]

又学到了这样一些内容
[b]1.awt提供的组件是各类操作系统的一个交集
2.swing提供的组件是各类操作系统是一个并集
swing和awt都可以实现平台的无关性(也就是java的本质),而swt据说可以,但实际上没做得swing跟awt好
[/b]
由于目标是了解awt和swing的整体的结构,所以对一些细节问题没太留意,因此在这两篇文章中学到的也十分地有限

由于找不到太有用的资料,只能从java api的包的树结构开始去学习,发现swing中比较重要而基础的JComponent是继承自awt的Component的,所以决定从awt包看起

于是找了比较简单的类开始入手——Label
[b]1.该类的确应该是设计为线程安全的,里面大部分方法都做了锁的相关操作
2.该类很简单
[color=red]3.发现一个叫“peer”的概念(对等体)[/color][/b]

于是大概翻了下Component的源码,发现处处有peer的痕迹,于是很自然地了解到搞懂peer的概念可能是读懂awt的关键

于是很幸运地找到了这篇文章
[url="http://colin2wang.iteye.com/blog/766861"]细说Java GUI:AWT,SWT,Swing[/url]
[url="http://blogs.sun.com/Swing/entry/awt_swt_swing_java_gui"]英文原文[/url]

里面有用的内容非常多,在这里稍微整理一下作为以后回忆的笔记:
1.原生组件和模拟组件
原生组件:也就是调用本地方法,使用操作系统提供的api,awt就是典型的代表
模拟组件:swing的做法,自己模拟画图
[b]2.历史:java的swing是由IBM以前的对头公司的开发团队来开发的,swt是由ibm开发的[/b]
[b]3.java本质:write once, run anywhere[/b]
swt在平台兼容性方面是比不上awt和swing的
“尽管Steve Northover,SWT之父,辩称SWT是平台无关的,你可以很容易地发现许多Windows API的痕迹。”
[b]4.awt用的是原生组件,它支持的组件是java所有操作系统的交集,而swing跟swt是并集,具体实现上swing大部分(除了顶层容器)都用模拟组件(纯java实现),而swt是只有平台不支持的才用模拟[/b]
[b]5.对等体(peer):awt中主要使用的是对等体实现平台无关性,它的主要行为也是由对等体来调用jni来实现的(主要的行为都发生在这里),这也解释了为什么源码中只有极少的代码[/b]
6.swt也使用了对等体,但实现方式跟awt有所不同,区别在于一个是纯jni实现,一个在jni的基础上封装了一层,以达到多个操作黏合在一起形成一个更有意义的操作
7.swing没有使用jni,“Swing将组件自己的数据结构存储在JVM的空间中。它完全由自己管理画图处理,事件分发和组件布局。”它的底层是java 2D的api[/b]
[b]8.swt需要显性地释放资源,awt由系统管理[/b]
[b]9."Swing组件在操作系统中没有相应的对等体。它只是一块顶层容器中的逻辑区域,实际上它从顶层容器的对等体中借用资源。"[/b]
"Swing组件与顶层AWT容器共享一个对等体。"
"当操作系统开始更新UI的时候,顶层容器和Swing组件总是先于AWT组件绘制。当它们完成绘制,AWT组件会覆盖Swing可能绘制过的地方。因此不提倡Swing和AWT组件的混用。"
10.look and feel观感(不太了解具体含义,是否是指类似renderer,editor这样的?):原生组件无法完成的东西,因为它依赖于操作系统的实现,但模拟组件却是可以模拟任何不存在的组件。
"Java 2D在Java中是强大的类库,它为高级图像处理,颜色管理,图形绘制和填充,坐标系变换和字体生成提供丰富的特性"
[b]11.又谈到了多线程与单线程模型[/b]
[b][color=red]12.谈到了想要仔细了解的一个重点:事件分发线程[/color][/b]
"AWT读取操作系统中的基本事件并在java代码中处理它们。AWT事件分发线程被命名为?AWT-{OS}-Thread。这个线程由AWT系统隐式启动。当AWT应用程序启动时,这个线程在背后启动,在操作系统上抽取和移除事件。当诸如按钮动作这样的逻辑事件被触发时,它传递给注册在按钮上的操作监听器。开发者也能为AWT组件编写鼠标,键盘和绘制事件的事件监听器。这通常通过扩展AWT Canvas组件来完成。这一组件支持了所有已提供的事件,而且你可以通过重写事件处理方法,实现一个自定义的组件。"
"然而,在Swing中,这是一个不同的线程。Swing把AWT事件作为自身事件系统的一个输入。它获取AWT事件并做一些初始化处理,产生一个对应的Swing事件并把它放到自己的事件队列上。Swing也有自己的事件分发线程,通常命名为EventQueue-0。这个线程从事件队列中抽取Swing事件,就如同AWT从操作系统中抽取那样。然后它把事件分发给目标组件。通常事件首先被分发给组件的顶层容器,然后由顶层容器的dispatch方法处理,它可能被再分发或重定向到一个Swing组件,在那里继续由自己的监听器进行处理。"
"SWT更类似于AWT。唯一的区别是它要求开发者显式地书写事件循环代码。然而底层的实现细节是不同于AWT的。看看SWT的读取和分发事件代码,它会让你想起MFC的代码风格。"
"AWT,SWT和Swing都有相似的事件监听器模型。它们都使用观察者模式,组件和监听器的连接方式是把监听器添加到组件上,这组成了一个对象网络的模型。当事件被触发并分发给组件,组件调用它的监听器以处理事件。一个监听器也可以继续分发事件给后续的处理,或产生一个新的事件并把它广播到网络中的其他节点上。基本上有两种不同的广播事件方式。一种是同步调用监听器。另一种是异步地将事件发送回队列,它将在新一轮的事件分发中被分发出去。
除了直接发送给队列的方式,Swing还有一些其他的分发异步事件的方法。其中之一是调用SwingUtilities 或EventQueue 的invokeLater,这一方法包装一个Runnable对象到事件中并把它发送给事件队列。这确保了Runnable的run方法能在事件分发线程上执行,保证了线程安全。实际上,Swing的Timer好SwingWorker基于这一机制实现。SWT也有相应的发送异步事件的方式。它的invokeLater的对应方法是display.asyncExec,它以一个Runnable对象作为参数。"

最后两段的内容其实《concurrency in practice》也提到了,而且讲得还详细些。
13.系统地描述了awt,swt,swing各自的优劣[/b]
基本也就是原生组件跟模拟组件各自的优劣
[b]14.swing扩展组件的方法:[/b]
A.继承已有组件;
B.靠复合组件的方式扩展。
C.从零开始使用JComponent编写自定义组件;
D.使用渲染器和编辑器机制扩展复制的Swing组件,如JList,JComboBox,JTable,JTree等;
E.基于已有Look And Feel 或从零开始创建新的Look And Feel;
F.使用LayeredPane,GlassPane或拖放机制开发高级的组件,例如浮动的固定组件,自定义弹出窗口,自定义菜单等。
[b]15.数据输送时间[/b]:数据输送时间是指用于将应用程序数据传递给UI组件所需要的时间。例如,一个学生管理系统要求从数据库中装载学生信息并在一个表格中显示出来。花费在从内存到表格组件的数据传递时间被称为数据输送时间.
在数据传送时间上,swing更有优势,
a.swing有许多设计良好的组件模型(model)
b.swing相比awt和swt少了一个从jvm到操作系统的数据转换过程(awt/swt需要从操作系统到jvm,然后再从jvm到操作系统,swing是直接画出来)

看完该文章后,再次定位到三个学习目标
[b]1.事件分发线程和它的观察者模式是了解awt的关键(其他的就是原生组件引用peer的事了),同时swing也需要了解这一点,swing相当于是在awt的事件线程上做的第二次的分发
2.java 2D的实现:也就是渲染的过程(这一点需要权衡一下,如果需要占用太多时间,可只是基本了解或者放弃,个人认为与操作系统关联性会很高,而且以后用到的机会少)
3.据文章描述,swing的api是设计得非常良好的api,参考它的可扩展性设计可能会对以后的工作有很大的帮助[/b]

接着,自然是先找到这个事件线程了,然后看它事件是怎么收的,然后又怎么进行分发的,然后是每个组件收到事件后怎么做,或者说怎么进行二次分发?
怎么找事件线程?用了个很简单的办法,写了点测试代码,然后debug,直接就得到了调用栈,然后找到了这个事件调度线程:EventDispatchThread
再往深点看一下,因为关联了jdk的源码,直接断了个断点在EventDispatchThread的构造函数中,然后又得到了EventDispatchThread的创建的调用栈
从最底部的main看起,发现了一件有点意思的事情(Component中的)
show方法是被废弃掉的,但推荐使用的setVisible居然调用的是show()~,那废弃干嘛呢?
    public void setVisible(boolean b) {
show(b);
}

找了找JComponent的setVisible,发现除了调用super.setVisible(Component的)之外,还重画了组件
    public void setVisible(boolean aFlag) {
if(aFlag != isVisible()) {
super.setVisible(aFlag);//注意,掉用了super的,即会一直往上走,直到调用Component的该方法,即是也会用原生组件来重画该组件,这明显与文章中借用顶层的资源来画图的说法不一致
Container parent = getParent();
if(parent != null) {
Rectangle r = getBounds();
parent.repaint(r.x,r.y,r.width,r.height);
}
// Some (all should) LayoutManagers do not consider components
// that are not visible. As such we need to revalidate when the
// visible bit changes.
revalidate();
}
}

这就恍然大悟了,估计是在做swing的时候为了统一接口将show给废弃的
但又发现了另外一个问题:据文章描述,swing中的组件是借用的是上层组件的对等体peer,如果让我们来做,想必是通过某种途径(主要应该由JComponent来覆盖掉Component的方法,找到顶层组件的peer),然后采用借用资源,然后进行绘图,那为什么他还是调用了super.setVisible?
[color=red]不过这个问题可能还会关联到更多的问题,暂时先看下去,作为遗留问题处理吧[/color]


再往上走,于是开始看到的对等体peer相关的代码,然后开始研究EventQueue的源码
经过粗略的分析,总结出以下几点
[b]awt的EventQueue提供了生产消费者模式,生产者是AWT-Windows线程(下面简称w),消费者是AWT-EventQueue-0(简称e),下面的图片可看出一些端倪
w线程主要由本地方法eventloop来循环拿到系统事件,然后是根据不同系统对应的Toolkit,然后执行EventQueue的postEvent方法将事件放入到队列中,之后调用了EventQueue的wakeup方法,唤醒e线程取数据,进行分发。
e线程负责对事件进行分发,分发完毕后则阻塞在getNextEvent方法,等待唤醒
在锁策略方面只用了固有锁(那时候并发包还没有~),因为只有一个队列,只需要在入队跟出队时上锁就可以了[/b]
[img]http://dl.iteye.com/upload/attachment/401967/30cb19d6-ca9d-39af-ae7c-031c296977f7.png[/img]

简单画了个流程图
[img]http://dl.iteye.com/upload/attachment/402124/33602c15-a7db-38d4-8e84-45021cf16b0e.png[/img]

再看一下EventQueue在细节上的实现
[b]1.虽然是个EventQueue,但其实封装了四个不同优先级的队列,同时只允许一个线程对Event进行操作,其中最高优先级的两种事件都是peer(对等体)产生的事件,paint或者update事件是最低的优先级,其他的为普通优先级(该举措能提高响应率)
2.存放了一个消费者的引用
3.虽然说awt只有一个队列(书中,文章中都这样说,图也这样画了),但其实是两个队列,一个是EventQueue,如上所说,包含4个优先级,同时提供消费者去取事件的方法,一个是PostEventQueue,它存放所有事件,相当于一个缓冲区,先经过该队列,才会放到真正的EventQueue中去。
4.接着是做gui的必须熟悉的方法了:invokeLater和invokeAndWait[/b]
    public static void invokeAndWait(Runnable runnable)
throws InterruptedException, InvocationTargetException {
if (EventQueue.isDispatchThread()) {
throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
}
class AWTInvocationLock {}
//每个进入的方法都创建一个
Object lock = new AWTInvocationLock();
//将锁传进去,到执行完毕后执行notifyAll
InvocationEvent event =
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock,
true);

synchronized (lock) {
Toolkit.getEventQueue().postEvent(event);
//等待,任务执行完毕后会执行notifyAll,然后继续往后执行
lock.wait();
}
Throwable eventThrowable = event.getThrowable();
if (eventThrowable != null) {
throw new InvocationTargetException(eventThrowable);
}
}

5.awt提供了接口实现一种叫联合事件的东西(不过貌似不推荐使用),不过总是一个思路嘛,所以在这也记录一下:

/*
* Should avoid of calling this method by any means
* as it's working time is dependant on EQ length.
* In the wors case this method alone can slow down the entire application
* 10 times by stalling the Event processing.
* Only here by backward compatibility reasons.
*/

只需要覆盖Component的isCoalescingEnabled方法和coalesceEvents(AWTEvent existingEvent,AWTEvent newEvent)方法就可以了,前提是事件源相同,event id相等,event id可以参考下AWTEvent的id

至此,生产者和队列的代码和思路已基本清晰了,至于本地方法执行的内容,也基本可以猜出来了,下一步就是理解消费者的行为了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值