2.6 Aplety#
Java jest najczęściej kojarzona z narzędziem przeznaczonym do pisania specjalnych programów tzw. apletów, umieszczanych na stronach WWW. Do tej pory, struktura Javy omawiana była na przykładzie aplikacji. W tym rozdziale zajmiemy się strukturą i sposobem pisania apletów.
Aplety od aplikacji różni środowisko, w którym są wykonywane, struktura programu oraz sposób wykonania. W aplikacji działanie rozpoczyna się od metody main, natomiast aplet nie zaczyna działania od metody main lecz ma swoją strukturę przedstawioną na rysunku: Cykl życia Apletu.
Przykłady podstawowych apletów#
Przykładowa prosta aplikacja w Javie wypisująca na ekranie słowa: “Hello World” ma postać:
class HelloWorldApp
{
public static void main(String[] args)
{
//Wyświetla na ekanie string
System.out.println("Hello World!");
}
}
Natomiast aplet, który ma zrobić to samo, wygląda następująco:
import java.applet.Applet;
import java.awt.Graphics;
public class HelloWorld extends Applet
{
public void paint(Graphics g)
{
g.drawString("Hello world!", 50, 25);
}
}
Umieszczenie apletu na stronie WWW wymaga dodania nowego znacznika (ang. tag) <APPLET> . . . </APPLET>.
Plik HTML z apletem Javy HelloWorld przyjmuje zatem postać:
<HTML><HEAD><TITLE> Przykładowy aplet </TITLE></HEAD>
<BODY>Tutaj jest wynik działania mojego apletu:
<APPLET CODE="HelloWorld.class" WIDTH=150 HEIGHT=25></APPLET>
</BODY>
</HTML>
gdzie znacznik <APPLET> ma m.in. następujące atrybuty:
- CODE - określa nazwę pliku z kodem bajtowym apletu,
- WIDTH i HEIGHT- początkowa szerokość i wysokość okna na stronie WWW, w którym aplet będzie wykonywany,
- CODEBASE - określa ścieżkę do katalogu zawierającego plik z kodem bajtowym apletu,
Po załadowaniu strony HTML do przeglądarki Internetowej na załadowanej stronie zobaczymy wynik działania apletu:
Ilustracja 2-12 Wynik działania apletu HelloWorld.
Każdy aplet dziedziczy z klasy java.applet.Applet: posiada więc metody zdefiniowane w tej klasie i odziedziczone z nadklas klasy java.lang.Applet co zobrazowano na poniższym rysunku.
Rysunek 2-7 Hierarchia dziedziczenia klasy Applet
Cykl życia apletu#
Do głównych metod odpowiedzialnych za przepływ sterowania podczas działania apletu należą: init, start, stop, destroy oraz metoda paint. W przykładowym aplecie, mamy redefinicję tylko jednej metody: paint (pozostałe są dziedziczone z klasy java.applet.Applet), która jest wykonywana za każdym razem, gdy zaistnieje potrzeba wykreślenia apletu (metoda paint, zostanie omówiona dalej w tym rozdziale).
Znaczenie pozostałych metod jest następujące:
- init - wywoływana tylko raz, gdy strona WWW zawierająca aplet zostanie po raz pierwszy “załadowana”, jeśli opuścimy stronę WWW zawierającą aplet i wrócimy na nią, metoda init nie będzie wykonana ponownie,
- start - metoda jest wykonywana za każdym razem, gdy strona, na której znajduje się aplet, staje się stroną bieżącą w przeglądarce,
- stop - metoda ta jest wykonywana za każdym razem, gdy do przeglądarki ładowana jest następna strona WWW,
- destroy - wykonywana gdy aplet kończy swoje działanie.
Nie każdy aplet musi redefiniować (ang. override) którąś z tych metod. Dla przykładu, prosty aplet zdefiniowany wcześniej nie redefiniuje żadnej z tych metod, dlatego, że służy on tylko do rysowania na ekranie słów “Hello World!”.
Metoda init powinna zawierać kod, który zazwyczaj znajduje się w konstruktorze. Jest tak dlatego, ponieważ w konstruktorze apletu nie jest zagwarantowane, że dostępne jest całe środowisko potrzebne do inicjalizacji apletu. Przykładowo, metoda ładująca obrazki nie jest dostępna w konstruktorze apletu.
Zazwyczaj aplet, musi redefiniować metodę start. Metoda start odpowiada za wykonanie pracy apletu, lub uruchamia wątki, które wykonują pracę apletu.
Większość apletów, które redefiniują metodę start powinny także redefiniować metodę stop. Metoda stop wstrzymuje wykonanie apletu, tak więc zasoby systemowe, zajęte przez aplet, nie są zwracane nawet, jeśli użytkownik nie ogląda właśnie strony zawierającej aplet. Przykładowo, aplet wyświetlający animację powinien w metodzie stop zatrzymać próby rysowania, gdy użytkownik ogląda już następną stronę.
Metody start i stop często używane są do tworzenia, uruchamiania oraz odpowiednio zatrzymywania i niszczenia wątków (patrz).
Wiele apletów nie używa metody destroy, ponieważ metoda stop przeważnie wykonuje wszystkie operacje potrzebne do zakończenia działania apletu. Mimo tego, użycie metody destroy jest możliwe w przypadku apletów, które tego jednak wymagają. Stosując metodę destroy można, zwolnić zasoby zarezerwowane w metodzie init;
Rysunek 2-8 Cykl życia Apletu
Ilustracją cyklu życia apletu jest następujący program:
Przykład 2.28 Kod apletu AppletLifeCycle
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Font;
public class AppletLifeCycle extends Applet
{
private int m_nInit;
private int m_nStart;
private int m_nStop;
private int m_nDestroy;
public void init()
{
m_nInit = 1;
showStatus("Wywołano metodę init");
}
public void start()
{
m_nStart = 1;
showStatus("Wywołano metodę start");
}
public void stop()
{
m_nStop = 1;
showStatus("Wywołano metodę stop");
}
public void destroy()
{
m_nDestroy = 1;
showStatus("Wywołano metodę destroy");
}
public void paint(Graphics g)
{
g.setFont(new Font("Arial", Font.BOLD, 12));
g.drawString("Liczba wywołań metody init: " + m_nInit, 10, 20);
g.drawString("Liczba wywołań metody start: " + m_nStart, 10, 40);
g.drawString("Liczba wywołań metody stop: " + m_nStop, 10, 60);
g.drawString("Liczba wywołań metody destroy: " + m_nDestroy, 10, 80);
}
}
Ograniczenia apletów#
Aplety działają w ramach specjalnego środowiska bezpieczeństwa nazywanego “sandbox”. Oznacza to, że aplety mają pewne ograniczenia:
- Nie mogą czytać ani zapisywać plików na dysku lokalnym użytkownika
- Nie mogą łączyć się z hostami innymi niż serwer, z którego zostały pobrane
- Nie mogą uruchamiać programów na komputerze użytkownika
- Nie mogą ładować bibliotek natywnych
- Nie mogą definiować natywnych metod
Te ograniczenia są wprowadzone ze względów bezpieczeństwa, aby zapobiec potencjalnie szkodliwym działaniom apletów pobranych z sieci.
Komunikacja z przeglądarką#
Aplety mogą komunikować się z przeglądarką za pomocą kilku metod:
Metoda showStatus()#
public void showStatus(String msg)
Metoda ta wyświetla podany tekst w pasku statusu przeglądarki.
Metoda getDocumentBase()#
public URL getDocumentBase()
Zwraca URL dokumentu HTML, w którym osadzony jest aplet.
Metoda getCodeBase()#
public URL getCodeBase()
Zwraca URL katalogu zawierającego klasy apletu.
Metoda getParameter()#
public String getParameter(String name)
Pozwala na pobieranie parametrów przekazanych do apletu w znaczniku <APPLET>.
Przykład użycia parametrów:
<APPLET CODE="MyApplet.class" WIDTH=300 HEIGHT=200>
<PARAM NAME="color" VALUE="red">
<PARAM NAME="speed" VALUE="5">
</APPLET>
public class MyApplet extends Applet
{
private String color;
private int speed;
public void init()
{
color = getParameter("color");
String speedStr = getParameter("speed");
if (speedStr != null) {
speed = Integer.parseInt(speedStr);
}
}
}
Rysowanie w apletach#
Rysowanie w apletach odbywa się głównie za pomocą metody paint(). Ta metoda jest wywoływana automatycznie przez system w następujących przypadkach:
- Gdy aplet jest wyświetlany po raz pierwszy
- Gdy okno apletu zostanie odsłonięte po tym, jak było zasłonięte
- Gdy okno apletu zostanie powiększone
- Gdy zostanie wywołana metoda
repaint()
Przykład rysowania podstawowych kształtów#
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Color;
public class DrawingApplet extends Applet
{
public void paint(Graphics g)
{
// Rysowanie linii
g.setColor(Color.red);
g.drawLine(10, 10, 100, 100);
// Rysowanie prostokąta
g.setColor(Color.blue);
g.drawRect(50, 50, 100, 80);
// Rysowanie wypełnionego prostokąta
g.setColor(Color.green);
g.fillRect(200, 50, 100, 80);
// Rysowanie okręgu
g.setColor(Color.yellow);
g.drawOval(350, 50, 80, 80);
// Rysowanie wypełnionego okręgu
g.setColor(Color.magenta);
g.fillOval(450, 50, 80, 80);
}
}
Podwójne buforowanie#
Dla apletów z animacją ważne jest stosowanie techniki podwójnego buforowania, aby uniknąć migotania:
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Dimension;
public class DoubleBufferedApplet extends Applet
{
private Image offscreenImage;
private Graphics offscreenGraphics;
private Dimension offscreenSize;
public void update(Graphics g)
{
Dimension d = getSize();
if ((offscreenImage == null) ||
(d.width != offscreenSize.width) ||
(d.height != offscreenSize.height))
{
offscreenImage = createImage(d.width, d.height);
offscreenSize = d;
offscreenGraphics = offscreenImage.getGraphics();
}
offscreenGraphics.clearRect(0, 0, d.width, d.height);
paint(offscreenGraphics);
g.drawImage(offscreenImage, 0, 0, null);
}
}
Obsługa zdarzeń w apletach#
Model tradycyjny (Java 1.0)#
W wczesnych wersjach Javy obsługa zdarzeń odbywała się poprzez przedefiniowanie odpowiednich metod:
import java.applet.Applet;
import java.awt.Event;
import java.awt.Graphics;
public class EventApplet extends Applet
{
private int mouseX, mouseY;
private boolean mousePressed = false;
public boolean mouseDown(Event evt, int x, int y)
{
mousePressed = true;
mouseX = x;
mouseY = y;
repaint();
return true;
}
public boolean mouseUp(Event evt, int x, int y)
{
mousePressed = false;
repaint();
return true;
}
public boolean mouseDrag(Event evt, int x, int y)
{
mouseX = x;
mouseY = y;
repaint();
return true;
}
public void paint(Graphics g)
{
if (mousePressed) {
g.fillOval(mouseX - 5, mouseY - 5, 10, 10);
}
}
}
Model delegacyjny (Java 1.1+)#
import java.applet.Applet;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class EventApplet extends Applet implements ActionListener
{
private Button button;
public void init()
{
button = new Button("Kliknij mnie!");
button.addActionListener(this);
add(button);
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == button) {
showStatus("Przycisk został kliknięty!");
}
}
}
Wielowątkowość w apletach#
Aplety często wykorzystują wielowątkowość, szczególnie w przypadku animacji lub długotrwałych operacji:
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Color;
public class AnimationApplet extends Applet implements Runnable
{
private Thread animationThread;
private int x = 0;
private boolean running = false;
public void init()
{
setBackground(Color.white);
}
public void start()
{
if (animationThread == null) {
running = true;
animationThread = new Thread(this);
animationThread.start();
}
}
public void stop()
{
running = false;
animationThread = null;
}
public void run()
{
while (running) {
x += 2;
if (x > getSize().width) {
x = 0;
}
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
break;
}
}
}
public void paint(Graphics g)
{
g.setColor(Color.red);
g.fillOval(x, 50, 20, 20);
}
}
Ładowanie i wyświetlanie obrazów#
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
public class ImageApplet extends Applet
{
private Image image;
public void init()
{
image = getImage(getCodeBase(), "image.gif");
// Oczekiwanie na załadowanie obrazu
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(image, 0);
try {
tracker.waitForID(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void paint(Graphics g)
{
if (image != null) {
g.drawImage(image, 10, 10, this);
}
}
}
Odtwarzanie dźwięków#
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Button;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SoundApplet extends Applet implements ActionListener
{
private AudioClip sound;
private Button playButton, stopButton;
public void init()
{
sound = getAudioClip(getCodeBase(), "sound.au");
playButton = new Button("Odtwórz");
stopButton = new Button("Zatrzymaj");
playButton.addActionListener(this);
stopButton.addActionListener(this);
add(playButton);
add(stopButton);
}
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == playButton) {
if (sound != null) {
sound.play();
}
} else if (e.getSource() == stopButton) {
if (sound != null) {
sound.stop();
}
}
}
}
Komunikacja między apletami#
Aplety mogą komunikować się między sobą za pomocą kontekstu apletu:
import java.applet.Applet;
import java.applet.AppletContext;
public class CommunicationApplet extends Applet
{
public void sendMessage(String message)
{
AppletContext context = getAppletContext();
Applet otherApplet = context.getApplet("otherAppletName");
if (otherApplet != null && otherApplet instanceof MessageReceiver) {
((MessageReceiver) otherApplet).receiveMessage(message);
}
}
}
interface MessageReceiver
{
void receiveMessage(String message);
}
Metody pomocnicze klasy Applet#
Klasa Applet udostępnia szereg przydatnych metod:
Metody informacyjne#
// Pobieranie informacji o aplecie
String getAppletInfo();
String[][] getParameterInfo();
// Pobieranie rozmiarów
Dimension getSize();
int getWidth();
int getHeight();
Metody multimedialne#
// Ładowanie obrazów
Image getImage(URL url);
Image getImage(URL url, String name);
// Ładowanie dźwięków
AudioClip getAudioClip(URL url);
AudioClip getAudioClip(URL url, String name);
Metody komunikacji#
// Komunikacja z przeglądarką
void showStatus(String msg);
AppletContext getAppletContext();
// Pobieranie URL-i
URL getDocumentBase();
URL getCodeBase();
Testowanie apletów#
Appletviewer#
Aplety można testować za pomocą narzędzia appletviewer dostarczonego z JDK:
appletviewer MyApplet.html
Testowanie w przeglądarce#
Aplety można również testować bezpośrednio w przeglądarce, tworząc prostą stronę HTML:
<HTML>
<HEAD><TITLE>Test Apletu</TITLE></HEAD>
<BODY>
<H1>Mój Aplet</H1>
<APPLET CODE="MyApplet.class" WIDTH="400" HEIGHT="300">
Twoja przeglądarka nie obsługuje apletów Java.
</APPLET>
</BODY>
</HTML>
Najczęstsze problemy i rozwiązania#
Problem z ładowaniem klas#
// Sprawdzenie ścieżki do klas
public void init()
{
try {
// Kod apletu
} catch (Exception e) {
showStatus("Błąd: " + e.getMessage());
e.printStackTrace();
}
}
Problem z bezpieczeństwem#
Aplety podpisane certyfikatem mogą mieć rozszerzone uprawnienia:
import java.security.AccessController;
import java.security.PrivilegedAction;
public class PrivilegedApplet extends Applet
{
public void init()
{
try {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// Kod wymagający dodatkowych uprawnień
return null;
}
});
} catch (Exception e) {
showStatus("Brak uprawnień: " + e.getMessage());
}
}
}
Obsługa starych przeglądarek#
W latach 90. ważne było uwzględnienie różnic między przeglądarkami:
public class CompatibilityApplet extends Applet
{
public void init()
{
String browser = System.getProperty("browser");
String version = System.getProperty("browser.version");
// Dostosowanie funkcjonalności do przeglądarki
if (browser != null && browser.indexOf("Netscape") != -1) {
// Kod specyficzny dla Netscape
} else if (browser != null && browser.indexOf("Microsoft") != -1) {
// Kod specyficzny dla Internet Explorer
}
}
}
Zaawansowane techniki#
Wykorzystanie layoutów#
import java.applet.Applet;
import java.awt.*;
public class LayoutApplet extends Applet
{
public void init()
{
setLayout(new BorderLayout());
Panel topPanel = new Panel();
topPanel.setLayout(new FlowLayout());
topPanel.add(new Button("Przycisk 1"));
topPanel.add(new Button("Przycisk 2"));
Panel centerPanel = new Panel();
centerPanel.setLayout(new GridLayout(2, 2));
centerPanel.add(new Label("Etykieta 1"));
centerPanel.add(new TextField("Pole tekstowe"));
centerPanel.add(new Checkbox("Pole wyboru"));
centerPanel.add(new Choice());
add("North", topPanel);
add("Center", centerPanel);
add("South", new Label("Status"));
}
}
Praca z kolorami i fontami#
import java.applet.Applet;
import java.awt.*;
public class StyleApplet extends Applet
{
public void paint(Graphics g)
{
// Ustawienie tła
setBackground(new Color(240, 240, 240));
// Różne fonty
Font titleFont = new Font("Arial", Font.BOLD, 16);
Font textFont = new Font("Times", Font.PLAIN, 12);
Font codeFont = new Font("Courier", Font.PLAIN, 10);
// Rysowanie z różnymi fontami
g.setFont(titleFont);
g.setColor(Color.blue);
g.drawString("Tytuł apletu", 20, 30);
g.setFont(textFont);
g.setColor(Color.black);
g.drawString("Tekst opisowy", 20, 50);
g.setFont(codeFont);
g.setColor(new Color(0, 128, 0));
g.drawString("kod programu", 20, 70);
}
}
Podsumowanie#
Aplety Java były rewolucyjną technologią lat 90., umożliwiającą tworzenie interaktywnych aplikacji w przeglądarkach internetowych. Mimo że obecnie zostały zastąpione przez nowsze technologie, zrozumienie ich struktury i zasad działania pozostaje ważne dla poznania historii rozwoju technologii internetowych i programowania Java.
Kluczowe zagadnienia apletów:
- Cykl życia apletu (init, start, stop, destroy)
- Ograniczenia bezpieczeństwa i środowisko “sandbox”
- Rysowanie i animacja za pomocą AWT
- Obsługa zdarzeń (model tradycyjny i delegacyjny)
- Wielowątkowość w animacjach
- Multimedia (obrazy, dźwięki)
- Komunikacja z przeglądarką i między apletami
Aplety demonstrują wiele podstawowych koncepcji programowania Java, takich jak dziedziczenie, polimorfizm, obsługa zdarzeń i wielowątkowość, czyniąc je doskonałym narzędziem edukacyjnym dla nauki programowania w erze początków Internetu komercyjnego.