Swing, RAP(RCP), Android 程序开发中,GUI刷新实现方式对比

社区广播:运维派(Yunweipai.com)是国内最早成立的IT运维社区,欢迎大家投稿,让运维人不再孤寂的成长!

今天我们说一说基于Java语言的几种GUI程序开发中,GUI刷新实现方式的对比。

Swing,RAP(RCP也是一样),Android其界面刷新的原理都是一样的:界面刷新只有一个线程,我们称其为UIThread,所有刷新界面的操作(如更新进度条上的进度)都必须通过这个线程来操作,否则若通过外部线程直接操作的话,会导致数据不一致。
由于整个界面都由UITread负责更新,因此,UIThread只能用来执行更新界面的操作,其他的耗时的操作是不允许的,如连接网络服务器。否则就会出现界面无响应的现象。


我曾经想过为什么不能允许多个线程去刷新界面?
我想(这个是我个人想法欢迎拍砖),有两点原因:
1)界面的行为本身是单线程的,用户只能一次做一个操作:用户不可以一次在两个输入框里面输入文字。
2)由于第一点,因此多线程刷新界面带来不了任何好处,且这么做会代码与界面相关的操作都需要同步(synchronized)或使用队列,者会使系统变的更复杂,除此之外没有任何好处。

(当然,MFC界面开发与这个原理不一样)。

  • 我们先说Swing。
  • Swing曾经是垄断了Java语言的界面开发,当时几乎所有的跨平台的界面程序都使用Swing开发(典型的有Oracle客户度,DB2客户端等)。当然现在他被RCP(称为SWT更精确)取代了,但是我们还是有必要了解。
    Swing开发者都知道Swing中的UIThread被称为事件派发线程。一个GUI Client只有一个UIThread,由于Swing是C/S结构的,因此会简单一些。

    我们要在Swing中更新界面,必须将更新界面的操作放到事件派发线程中:

    SwingUtilities.invokeLater(runnable);
    SwingUtilities.invokeAndWait(runnable);

    if(SwingUtilities.isEventDispatchThread())
    {
    //just do it
    }
    else
    {
    //SwingUtilities.invokeLater(runnable);
    }

    invokeLater是不阻塞当前界面线程,放到事件派发线程的队列中去执行。实际上这个执行的延迟不会超过5秒,除非有用户交互的界面在等待用户反馈(如弹出框等待用户确认),否则界面自身就有问题了。
    invokeAndWait是等待界面线程执行,然后当前线程才返回。
    他们都需要接受一个Runnable对象的参数。
    我们还可以精细化处理,判断当前线程是否是事件派发线程,是就直接执行。

    在Swing界面中,如果点击某个按钮或菜单后,后台的响应需要耗时较长(如通过网络查询数据回来),则你需要使用后台线程来执行,并将事件派发线程suspend,并将界面置为忙等(鼠标漏斗形状)状态,等响应回来后,使用SwingUtilities.invokeLater方法将数据刷新到界面上。

    关于Swing多线程及界面刷新,可以参考 Threads and Swing

  • 再说说RAP
  • RAP是基于RCP的基础API,加上了一套Java代码转换为javascript代码的机制而形成的。准确说不是代码之间的转换,是RAP框架中定义了一套Widget,这套Widget的定义和行为可以用Java代码来开发(开发方法与RCP程序一模一样),然后RAP框架在运行时会将RCP的Widget转换为javascript的,然后在Web上运行。详细可以参考官方网站

    RAP中刷新界面,与Swing是类似的,也是一个Client只有一个UITrhead,RAP中就是称呼为:UITrehad。
    我们开发这写RAP代码刷新界面的时候,实际上是很简单的:

    Display display = getDisplay();
    display.syncExec(runnable);
    display.asyncExec(runnable);

    每个client有唯一一个Display对象,display.syncExec(runnable)是立即刷新界面,display.asyncExec(runnable)是将刷新事件放到UIThread的队列中排队刷新。分别对应SwingUtilities.invokeAndWait(runnable)和SwingUtilities.invokeLater(runnable)方法。

    但是,作为开发者还是要了解其中的差异,否则开发的代码有bug了自己都定位不出来。

    RAP是Web程序,本质上是javascript的。但是,与一般的Web程序还不一样,RAP的绘制界面的机制是:

    1)RAP使用了Qooxdoo作为JS框架;
    2)在应用程序加在的时候,会将Qooxdoo和RAP这两部分框架代码加在到浏览器;
    3)浏览器中的RAP框架请求sever绘制应用界面(如View,Tree等);
    4)server根据client发送过来的请求,返回对应的绘制界面的JS代码给client;--这里是UIThread处理的。
    5)client执行server发送过来的代码,绘制界面。
    6)后续新打开的界面,都是通过client请求server,server发送JS代码到浏览器执行生成界面。

    这里我们发现UIThread是处理client响应生成JS代码的,但是我们要知道,这个是RAP框架做的事情。
    对于我们使用RAP作为框架来开发的开发者,实际上执行到的就是构造我们的Composite或刷新界面的方法。
    为了更进一步的阐述,我们看下面的图:

    图片中地下方框S字母是一个RAP Server,通常运行在OSGi中。大的S方框中的每个小I方框是一个client实例(里面就包含一个UIThread),长方形的C方框是所有client共享的数据(如SessionStore等)。上面圆圈是client。
    这个结构我们可以看到与Swing是完全不一样的,RAP在Server端有多实例,多个UITrhead,还有共享的数据,比Swing要复杂很多。

    因此,我们可以想的到,UIThread s 与共享数据之间,必定存在比较复杂的锁同步机制。
    所以,我们在使用RAP开发的时候要特别注意,我们放到UIThread中执行的代码,要尽量少加锁,一定要加锁的话要小范围,并明确不会与RAP自身的锁造成死锁。否则很容易出现界面死锁的问题。

    从RAP 的bug库上来看,UIThread死锁的问题是最难解决的。

  • 最后说说Android
  • 从RAP复杂的同步锁里面出来后,你会觉得Android简单很多。
    从结构上来说,Android与Swing是一致的,C/S结构。

    Android的UIThread更新方法是:

    Activity.runOnUiThread(Runnable)

    这个方法也是一样,不允许做耗时的操作,否则界面会无响应。

    若一定要做耗时的操作,如点登录按钮,要连接到后台登录。那么推荐的方法是AsyncTask(当然你也可以自己些后台线程,但就走弯路了)。
    我们从一个例子来阐述AsyncTask的基本用法。

    //我们继承了AsyncTask,AsyncTask的模板参数定义的有点别扭。
    //第一个参数表示doInBackground函数的输入参数类型,这里是UserLoginReqJSON
    //第二个参数表示进度
    //第三个参数表示doInBackground返回值类型,且onPostExecute的输入参数
    private class LoginTask extends AsyncTask{

    //这个函数是执行耗时的后台操作,这里是登录后台
    @Override
    protected HandlerResultJSONHolder doInBackground(UserLoginReqJSON... arg0) {
    //user the LoginProc object to perform the login action
    HandlerResultJSONHolder resp_holder = new HandlerResultJSONHolder();
    LoginProc login_proc = UserJSONInvoker.getLoginProc(arg0[1], resp_holder);
    login_proc.run();
    return resp_holder;
    }

    //这个函数是刷新界面,不能做耗时的操作
    @Override
    protected void onPostExecute(HandlerResultJSONHolder resp_holder){
    if (resp_holder.getHandlerResultJSON().getErrorCode() == 0){
    Intent i = new Intent(getApplicationContext(), MainActivity.class);
    LoginActivity.this.startActivity(i);
    }else{

    }
    }

    }

    更多的关于Android多线程用法,请参考Processes and Threads

最后汇总比对,Swing和Android结构上相对RAP来说要简单,开发框架上Android更易用,毕竟Android是后出来的,符合编程的进化规则。RAP在结构上相对复杂,不容易把握,要些处稳定的程序,需要了解其原理,不要被其开发框架骗了。

网友评论comments

发表评论

电子邮件地址不会被公开。 必填项已用*标注

  1. XiaoMing说道:

    有个地方拼错词了:) UITrehad

    关于大多数GUI的界面操作实现为单线程的原因,应该主要不是用户没有需求,刚好看了《JAVA并发编程实践》9.1–为什么GUI是单线程化的。
    大意是说极易造成死锁,要稳定的实现多线程非常困难,即使实现了,普通的应用程序开发者也根本没法顺利的使用这个库。
    还有这个
    “Multithreaded toolkits: A failed dream?”
    作者是SUN公司的个牛人,英文不太好,没完全看完,最后总结说他也想看到有多线程的界面库,但实在没办法。

    对MFC完全不了解,网上搜了下似乎支持多事件线程(我不确定),但看多数建议也都是让把处理界面的都放到一个线程中去。

    做界面的方式好多,前段时间用wxPython+BOA画界面,做了个小工具界面简单,项目组用还挺好的。越学越觉得很多东西想学都忙不过来,现在想是不是有空看看android给自己手机搞个helloworld试一下。

  2. Lin说道:

    对RAP的很有研究啊

Copyright © 2012-2017 YUNWEIPAI.COM - 运维派 - 粤ICP备14090526号-3
扫二维码
扫二维码
返回顶部