今天我们说一说基于Java语言的几种GUI程序开发中,GUI刷新实现方式的对比。
Swing,RAP(RCP也是一样),Android其界面刷新的原理都是一样的:界面刷新只有一个线程,我们称其为UIThread,所有刷新界面的操作(如更新进度条上的进度)都必须通过这个线程来操作,否则若通过外部线程直接操作的话,会导致数据不一致。
由于整个界面都由UITread负责更新,因此,UIThread只能用来执行更新界面的操作,其他的耗时的操作是不允许的,如连接网络服务器。否则就会出现界面无响应的现象。
我曾经想过为什么不能允许多个线程去刷新界面?
我想(这个是我个人想法欢迎拍砖),有两点原因:
1)界面的行为本身是单线程的,用户只能一次做一个操作:用户不可以一次在两个输入框里面输入文字。
2)由于第一点,因此多线程刷新界面带来不了任何好处,且这么做会代码与界面相关的操作都需要同步(synchronized)或使用队列,者会使系统变的更复杂,除此之外没有任何好处。
(当然,MFC界面开发与这个原理不一样)。
- 我们先说Swing。
- 再说说RAP
- 最后说说Android
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是基于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死锁的问题是最难解决的。
从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在结构上相对复杂,不容易把握,要些处稳定的程序,需要了解其原理,不要被其开发框架骗了。
本文链接:http://www.yunweipai.com/672.html
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/53143.html