Showing posts with label SWT. Show all posts
Showing posts with label SWT. Show all posts

Tuesday, April 16, 2013

Tabris a cool way to reuse your Notes/Eclipse RCP skills in the mobile app development

Today the final version of Tabris has been released. Tabris is a native peer client for a RAP (Remote Application Plattform formerly Rich Ajax Plattform) Server. RAP is a mature technology to run SWT/Jface Applications and Eclipse RCP Plugins on a server and render the UI to different clients like a webbrowser or native clients. The RAP Server and the Webclient are opensource. With the Tabris SDK they first commercial native client to RAP Server for Android and IOS is available. So you can reuse your existing SWT/Jface skills and build native apps for Android and IOS right like you are building Plugins for Notes today. Demos of the Tabris functionality are availabe on the google playstore. I think this is really a very cool technology and every Eclipse RCP developer should try this out.

Demos of RAP for Webclients


Sunday, November 4, 2012

Java ist langsam, oder doch nicht?

Oft hört man den Vorwurf, dass Java langsam ist und nur C oder C++ Programme eine ordentliche Performance haben. Dies hat vielleicht für die ersten Versionen von Java gestimmt, aber seit Einführung von JIT Compilern stimmt dieser Vorwurf sicher nicht mehr. Warum sich Javaprogramme trotzdem oft ziemlich träge anfühlen, liegt weniger an der Geschwindigkeit der jvm sondern viel eher an der Unwissenheit der Programmierer die Javaprogramme erstellen. Ich möchte das heute an einem einfachen Beispiel zeigen.

In dem Beispiel erzeugen wir eine große SWT Tabelle wie es in vielen Anwendungen der Fall ist. Denken wir z.B. an eine Ansicht über den Kundenstamm. So eine Tabelle kann locker mal 100000 Einträge haben. Ich habe das ganze erst einmal so geschrieben, wie SWT Tabellen in den meisten Handbüchern erklärt werden. Zuerst wird ein Fenster erstellt. In dem Fenster die Tabelle und dann befülle ich über eine Schleife die Tabelle mit 100000 Zeilen. Das dauert auf meinem Rechner mit Quadcore CPU und reichlich Speicher unerträglich lange 15 Sekunden und zusätzlich zeigt mir der Systemmonitor einen Speicherverbrauch von unvorstellbaren 514 Megabyte für so ein simples Programm an.

Hier mal der Code wie es oft gemacht wird, man es aber bei größeren Tabellen auf keinen Fall machen sollte:
public class TestNormalTable {
 public static void main(String[] args) {
  Display display=new Display();
  Shell shell=new Shell(display);
  shell.setSize(600, 1024);
  shell.setLayout(new FillLayout());
  Table table=new Table(shell, SWT.BORDER);
  table.setLinesVisible(true);
  table.setHeaderVisible(true);
  //Spalten erstellen.
  {
  TableColumn spalte=new TableColumn(table,SWT.NONE);
  spalte.setText("Spalte 1");
  spalte=new TableColumn(table,SWT.NONE);
  spalte.setText("Spalte 2");
  }
  //Zeilen befüllen
  for(int i=0;i<100000;i++){
   TableItem zeile=new TableItem(table, SWT.NONE);
   zeile.setText(0,"Beispieltext zeile ");
   zeile.setText(1,new Integer(i).toString() );
  }
  //Spaltenbreiten optimieren.
  for(TableColumn spalte:table.getColumns()){
  spalte.pack(); 
  }
  shell.open();
  while(!shell.isDisposed()){
   if(!display.readAndDispatch()) display.sleep();
  }
  display.dispose();
 }
}

Screenshot des erstellen Fensters.



Bei oberflächlicher Betrachtung könnte man annehmen, dass Java einfach nicht schneller eine so simple Schleife mit 100.000 Wiederholungen durchlaufen kann. Wenn man das Programm aber durch einen Profiler schickt, dann sieht man sehr schnell, dass die verbrauchte Zeit und auch der verbrauchte Speicher gar nicht im Javacode steckt, sondern dass die meiste Zeit in nativen Code des Fenstertoolkit verschwendet wird. SWT zeichnet seine Komponenten zu denen auch die Tabelle gehört ja nicht selbst, sondern verwendet das native Fenstertoolkit der Plattform auf der das Programm läuft. In meinem Fall GTK+. Die Frage ist jetzt wie kann man vermeiden, dass bei einer so großen Tabelle alle nativen Tableitems erstellt werden müssen. Die Antwort ist ziemlich einfach und auch sehr effektiv. Man muss eine sogenannte Virtuelle SWT Tabelle verwenden.

Bei der virtuellen Tabelle wird die Tabelle mit dem style 'SWT.VIRTUAL' erstellt werden. Damit signalisiert man SWT, dass man eine Tabelle haben will, die dynamisch je nach Bedarf befüllt wird.

Hier mal der Code wie man eine hochperformante große Tabelle erstellt:
public class TestVirtualTable {

 /**
  * @param args
  */
 public static void main(String[] args) {
  final String[] spalte1=new String[100000];
  final int[] spalte2=new int[100000];
  Display display = new Display();
  Shell shell = new Shell(display);
  shell.setSize(600, 1024);
  shell.setLayout(new FillLayout());
  final Table table = new Table(shell, SWT.BORDER | SWT.VIRTUAL);
  table.setLinesVisible(true);
  table.setHeaderVisible(true);
  //Arraytabelle befüllen.
  for(int i=0;i<100000;i++){
   spalte1[i]="Beispieltext zeile";
   spalte2[i]=i;
  }
  // Spalten erstellen.
  {
   TableColumn spalte = new TableColumn(table, SWT.NONE);
   spalte.setText("Spalte 1");
   spalte = new TableColumn(table, SWT.NONE);
   spalte.setText("Spalte 2");
  }
  // Grösse der Tabelle festlegen
  table.setItemCount(100000);
  // Callback Listener zum Auslesen der Daten hinzufügen.
  // Immer wenn eine neue Zeile am Bildschirm angezeigt wird, wird dieser
  // Listener aufgerufen um Daten anzufordern.
  // Es werden also nur die Zeilen erstellt, die der Benutzer wirklich
  // anschaut.
  table.addListener(SWT.SetData, new Listener() {
   @Override
   public void handleEvent(Event event) {
    TableItem zeile = (TableItem) event.item;
    // Ermittelen des Zeilenindex. Im echten Leben würde man mit
    // diesem Index z.B. auf ein Array zugreifen.
    int index = table.indexOf(zeile);
    zeile.setText(0, spalte1[index]);
    zeile.setText(1, new Integer(spalte2[index]).toString());
   }
  });
  // Spaltenbreiten optimieren.
  // Da die virtuelle Tabelle erst nach dem Anzeigen befüllt wird, müssen
  // wir die Spaltenbreitenoptimierung in die Eventwarteschlange hinten
  // anfügen.
  // Damit wird die Spaltenbreitenoptimierung nach dem Anzeigen der ersten
  // Sätze ausgeführt und sollte schon gute Ergebnisse bringen.
  Display.getDefault().asyncExec(new Runnable() {
   @Override
   public void run() {
    for (TableColumn spalte : table.getColumns()) {
     spalte.pack();
    }
   }
  });
  shell.open();
  while (!shell.isDisposed()) {
   if (!display.readAndDispatch())
    display.sleep();
  }
  display.dispose();
 }
}

Diese Version wird auf meinen Rechner praktisch verzögerungsfrei angezeigt. Zusätzlich belegt sie nur etwas über 34 Megabyte Hauptspeicher. Bei völlig gleichem Ergebnis.

Hier nocheinmal im Detail die wichtigen Punkte:
  
  • Zeile 13 Die Tabelle mit dem style 'SWT.VIRTUAL' erstellen.
  • Zeile 29 mit setItemCount der Tabelle mitteilen wie viele Items die Tabelle hat. 
  • Zeile 35-45 einen Listener der der Tabelle hinzufügen, der jedes mal aufgerufen wird, wenn die Tabelle neue Items darstellen will. 
  • Zeile 52-59 Da die Tabelle vor dem anzeigen noch keine Items hat, funktioniert der pack zum Festlegen der Spaltenbreite nicht, wenn man es wie im Code ohne Virtual macht. Ich setze daher den Code in ein Runnable dass ich mit asyncExec in die Eventqueue stelle. Dieser Code, wird dann nach dem Anzeigen der Tabelle ausgeführt. Dann hat die Tabelle schon die Items der ersten Seite und die Spaltenbreitenoptmierung wird dann anhand dieser Daten gemacht.

Wie man an diesen Beispiel sieht, können oft schon kleine Änderungen an einem Programm den Unterschied machen ob das Programm ein quälend langsamer Ressourcenfresser oder hochperformant und effizient ist. Ich verwende die virtuellen Tabellen übrigens z.B. in einem Programm zur Anzeige der Änderungen im Journal unserer DB2 durchgeführt werden. Wenn man da die Änderungen eines ganzen Tages aufruft, dann kommen schnell mal 2 Millionen Änderungen zusammen. Auch diese riesen Tabellen werden Dank SWT.VIRTUAL ohne Verzögerungen angezeigt.

ad