[问题描述]最近在尝试重构一个用SWT写的图形客户端,这个过程中遇到很多问题,其中最显著的就是SWT的客户端经常发生卡死。所谓成也萧何,败也萧何。避免UI失去响应的关键就在于下面这段代码
1 2 3 4 5
| while (!shell.isDisposed()) { if (!Display.getDefault().readAndDispatch()) { Display.getDefault().sleep(); } }
|
以上这段代码的作用是,当在父面板打开一个新的子面板时,调用这段代码,可以将此操作后面的程序都阻塞,直到子面板被关闭才会继续执行后面的代码。在这种情况下,焦点在新打开的这个面板上,ui线程被阻塞,是无论如何不会失去响应的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void open() { shell.open(); shell.layout(); while (!shell.isDisposed()) { if (!Display.getDefault().readAndDispatch()) { Display.getDefault().sleep(); } } }
public void operat(){ open(); xxx(); ... ... }
|
因为SWT只有一个UI线程,Display在所有线程中为单例,若将阻塞线程的这段代码屏蔽掉,则又会碰到另一个新的问题:
如下代码所示:当open后面的操作为耗时和不耗时两种情况时,就会产生两种不同的结果
- 不耗时:程序执行完open操作后,不会阻塞后面的xxx操作,xxx操作会在后台继续执行
- 耗时:因为子面板被打开后又没用阻塞线程,在开始一段时间内后面的xxx操作继续在后台执行,但是当执行时间过长时,SWT子面板长时间未响应,就会被系统变成卡死的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void open() { shell.open(); shell.layout(); } }
public void 非耗时(){ open(); xxx(); }
public void 耗时(){ open(); xxx(); ... ... }
|
因为验证登录是一个耗时操作,所以我想在点击登录按钮时弹出一个dialog就碰到尴尬的问题了:
- 如果我阻塞线程,程序绝不会卡死,但是只有将dialog关闭后,才会继续执行后面的联网验证登录操作
- 如果我屏蔽阻塞线程的代码,网络状态良好的情况下,也不会发生卡死的状况,一旦网络状况不太好,程序就极容易失去响应
Eclipse在启动时的加载画面有时也会失去响应,该不会真的没有解决办法了吧。
[问题解决] 毕竟SWT是个比较古老的东西,我只能试着看看能不能找到答案。我无意间发现jface中有一个org.eclipse.jface.dialogs.ProgressMonitorDialog
,看起来很符合我们的需求
1 2 3 4 5 6 7 8 9 10 11 12 13
| ProgressMonitorDialog progress = new ProgressMonitorDialog(getShell()); progress.setCancelable(true); try { progress.run(true, true, new IRunnableWithProgress(){ @Override public void run(IProgressMonitor arg0) throws InvocationTargetException, InterruptedException { doOperate(); } }); } catch (InvocationTargetException | InterruptedException e) { }
|
在ProcessMonitorDialog
的使用中,将耗时操作封装成了一个IRunnableWithProcess
,在渲染出GUI之后,在后台执行我们想要执行的耗时操作。根据ProcessMonitorDialog
设计的提示,那我们也可以很容易使用SWT根据我们的需求定制我们想要的dialog:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| public class ShowLoadingDialog{
private Shell shell; private Label label;
private String title; private String text;
private Thread job ;
private ShowLoadingDialog(Shell shell, String title, String text) { this.shell = shell; label = new Label(shell, SWT.NONE);
this.text = text; this.title = title; }
public static ShowLoadingDialog getDialog(Shell parentShell, String title, String text){ Shell shell = new Shell(parentShell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); return new ShowLoadingDialog(shell, title, text); }
public void open() { setDialog(); shell.open(); shell.layout(); while (!shell.isDisposed()) { if (!Display.getDefault().readAndDispatch()) { Display.getDefault().sleep(); } } }
public void close(){ shell.dispose();
}
public void run(Runnable runnable){ job = new Thread(runnable);
Thread scan = new Thread(){ public void run(){ while(job.isAlive()) if(shell.isDisposed()) job.stop();
Display.getDefault().asyncExec(new Runnable() { @Override public void run() { close(); } });
} };
job.start(); scan.start(); open(); }
protected void setDialog() { MacSetFullScreenUtil.setFullScreen(shell);
Composite parent = shell.getParent(); Point parentPoint = parent.getLocation(); int parentW = parent.getSize().x; int parentH = parent.getSize().y;
int shellW = 345; int shellH = 181; int locX = parentPoint.x + (parentW - shellW)/2; int locY = parentPoint.y + (parentH - shellH)/2;
shell.setBounds(locX , locY, shellW, shellH); shell.setText(title);
int labW = 204; int labH = 44; int labX = (shellW - labW)/2; int labY = (shellH - labH)/2; label.setAlignment(SWT.CENTER); label.setText(text); label.setBounds(labX, labY, labW, labH); }
public Thread getJob(){return this.job;}
}
|