Swing, RAP(RCP), Android 程式開發中,GUI重新整理實現方式對比
今天我們說一說基於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方法將資料重新整理到介面上。
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{
}
}
最後彙總比對,Swing和Android結構上相對RAP來說要簡單,開發框架上Android更易用,畢竟Android是後出來的,符合程式設計的進化規則。RAP在結構上相對複雜,不容易把握,要些處穩定的程式,需要了解其原理,不要被其開發框架騙了。