MIDlet으로 살펴보는 Java ME 피처폰 애플리케이션 구조

Java ME와 MIDP 위에서 동작한 MIDlet의 생명주기, LCDUI·RMS 개발 방식, JAR·JAD 배포 구조를 정리한다.

2026-08-14 · 최초 발행 2026-01-12

피처폰 런타임에서 애플리케이션이 시작되는 지점

MIDlet은 Java ME(Micro Edition) 환경에서 실행되는 모바일 애플리케이션의 기본 단위다. MIDP(Mobile Information Device Profile)를 기반으로 하며, 제한된 메모리와 CPU를 가진 피처폰에서 동작하도록 설계됐다.

javax.microedition.midlet.MIDlet 클래스를 상속한 애플리케이션은 실행 시작, 일시정지, 종료를 생명주기로 관리한다. 화면은 LCDUI(LCD User Interface)로 만들고, 기능 구현에는 비즈니스 로직과 이미지·사운드·데이터 같은 리소스가 결합된다. 애플리케이션은 샌드박스 안에서 격리 실행된다.

2000년대 초반 휴대폰 보급과 함께 게임, 링톤, 유틸리티 같은 모바일 콘텐츠 수요가 늘었다. 그러나 Nokia Series 40, Samsung SGH처럼 제조사마다 별도 플랫폼을 제공하면서 이식성과 개발 생산성이 문제가 됐다. Java ME는 표준 API인 MIDP와 Write Once, Run Anywhere라는 방향으로 이 간극을 줄이려 했다.

피처폰환경Java ME플랫폼MIDPMIDlet모바일애플리케이션게임유틸리티소셜콘텐츠

CLDC와 MIDP가 제공한 실행 환경

J2ME 플랫폼은 Configuration, Profile, Optional Packages로 나뉜다. CLDC(Connected Limited Device Configuration)는 제한된 JVM인 KVM과 핵심 라이브러리를 포함한 최소 Java 런타임을 제공한다. 그 위에 MIDP가 올라가 UI, 네트워킹, 저장 기능을 제공하며, WMA(Wireless Messaging API)의 SMS·MMS, MMAPI(Mobile Media API)의 멀티미디어, JSR-179의 Location API 같은 선택 패키지가 기능을 확장한다.

MIDP 1.0은 2000년에 기본 UI인 LCDUI, HTTP 네트워킹, RMS(Record Management System)를 제공했다. 2002년의 MIDP 2.0은 게임 API, HTTPS, Push Registry를 통한 백그라운드 실행, 멀티미디어 지원을 추가했다.

애플리케이션에서 주로 접하는 API는 다음과 같다. javax.microedition.lcdui는 UI 컴포넌트, javax.microedition.rms는 데이터 저장, javax.microedition.io는 네트워킹, javax.microedition.midlet은 MIDlet 클래스, javax.microedition.lcdui.game은 게임 API를 담당한다.

MIDlet Suite는 하나 이상의 MIDlet을 묶은 배포 단위다. .jar 파일에 MIDlet 클래스 파일, 이미지·사운드·텍스트 리소스, META-INF/MANIFEST.MF 메니페스트를 담고 필요하면 JAD(Java Application Descriptor) 파일을 함께 제공한다.

MIDlet-Name: MyApp
MIDlet-Version: 1.0.0
MIDlet-Vendor: MyCompany
MIDlet-1: MyMIDlet, /icon.png, com.example.MyMIDlet
MicroEdition-Profile: MIDP-2.0
MicroEdition-Configuration: CLDC-1.1
J2ME플랫폼Configuration(CLDC)Profile(MIDP)MIDletLCDUIRMSNetworkingGame API

실행 중인 MIDlet이 거치는 상태

MIDlet은 Paused, Active, Destroyed 상태를 갖는다. Paused는 초기 상태이자 백그라운드 상태로 자원 사용을 최소화하는 구간이다. Active에서는 화면을 표시하고 애플리케이션이 실행된다. Destroyed는 자원을 해제한 종료 상태이며 재시작할 수 없다.

이 상태 변화는 다음 추상 메소드로 구현한다.

public abstract class MIDlet {
    protected abstract void startApp()
        throws MIDletStateChangeException;

    protected abstract void pauseApp();

    protected abstract void destroyApp(boolean unconditional)
        throws MIDletStateChangeException;
}

startApp()은 Paused에서 Active로 전환하며 초기화와 UI 표시를 수행한다. pauseApp()은 Active에서 Paused로 바뀔 때 호출되며, 전화 수신이나 다른 앱 전환에 맞춰 상태를 저장하거나 선택적으로 자원을 해제한다. destroyApp(boolean unconditional)은 Paused 또는 Active에서 Destroyed로 전환될 때 정리 작업을 수행한다. unconditional = true면 무조건 종료하고, unconditional = false면 예외를 발생시켜 거부할 수 있다.

애플리케이션은 직접 일시정지나 종료를 요청할 수 있다.

// 자발적 일시정지 요청
notifyPaused();

// 자발적 종료 요청
notifyDestroyed();

AMS(Application Management Software)는 메모리 부족, 사용자 종료, 전화 수신 같은 시스템 이벤트로도 상태를 제어한다. 구현은 다음과 같은 형태다.

public class MyMIDlet extends MIDlet {
    private Display display;
    private Form form;

    public MyMIDlet() {
        display = Display.getDisplay(this);
        form = new Form("Hello World");
        form.append("Welcome to MIDlet!");
    }

    protected void startApp() throws MIDletStateChangeException {
        display.setCurrent(form);
    }

    protected void pauseApp() {
        // 상태 저장
    }

    protected void destroyApp(boolean unconditional)
        throws MIDletStateChangeException {
        // 리소스 정리
    }
}
startApp()pauseApp()destroyApp()destroyApp()PausedActiveDestroyed

LCDUI와 이벤트 처리로 화면을 구성하는 방식

MIDlet 클래스는 DisplayDisplayable을 통해 화면을 제어하고, CommandListener로 소프트키와 메뉴 입력을 받는다. 아래 예시는 종료 명령을 등록하고 notifyDestroyed()로 종료를 요청하는 기본 구조다.

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class HelloMIDlet extends MIDlet implements CommandListener {
    private Display display;
    private Form form;
    private Command exitCommand;

    public HelloMIDlet() {
        display = Display.getDisplay(this);
        form = new Form("Hello MIDlet");
        exitCommand = new Command("Exit", Command.EXIT, 1);
        form.addCommand(exitCommand);
        form.setCommandListener(this);
    }

    protected void startApp() {
        display.setCurrent(form);
    }

    protected void pauseApp() {}

    protected void destroyApp(boolean unconditional) {}

    public void commandAction(Command c, Displayable d) {
        if (c == exitCommand) {
            notifyDestroyed();
        }
    }
}

LCDUI의 High-Level API에는 폼 기반 UI를 위한 Form, 목록용 List, 입력을 위한 TextBox, 알림 대화상자인 Alert가 있다.

Form form = new Form("Title");
form.append("Hello");
form.append(new TextField("Name", "", 30, TextField.ANY));
form.append(new ChoiceGroup("Options", Choice.MULTIPLE));

직접 그래픽을 그려야 하는 경우에는 Low-Level API의 CanvasGraphics를 사용한다.

public class MyCanvas extends Canvas {
    protected void paint(Graphics g) {
        g.setColor(255, 255, 255);  // 흰색
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(0, 0, 0);  // 검정
        g.drawString("Hello", 10, 10, Graphics.LEFT | Graphics.TOP);
    }
}

명령 기반 이벤트 처리는 CommandCommandListener를 연결한다.

Command okCommand = new Command("OK", Command.OK, 1);
form.addCommand(okCommand);
form.setCommandListener(this);

public void commandAction(Command c, Displayable d) {
    if (c == okCommand) {
        // OK 처리
    }
}

게임처럼 키 입력을 직접 다루는 화면은 Canvas에서 키 코드를 게임 액션으로 변환해 처리한다.

public class GameCanvas extends Canvas {
    protected void keyPressed(int keyCode) {
        int gameAction = getGameAction(keyCode);
        switch (gameAction) {
            case UP:
                // 위로 이동
                break;
            case DOWN:
                // 아래로 이동
                break;
        }
        repaint();
    }
}

RMS에 로컬 데이터를 남기는 방법

RMS는 RecordStore를 사용하는 키-값 저장소이며, 데이터는 바이트 배열로 기록한다. 레코드를 추가한 뒤 열거자를 통해 읽는 기본 흐름은 다음과 같다.

// 저장
RecordStore rs = RecordStore.openRecordStore("myStore", true);
String data = "Hello, RMS!";
byte[] bytes = data.getBytes();
rs.addRecord(bytes, 0, bytes.length);
rs.closeRecordStore();

// 읽기
RecordStore rs = RecordStore.openRecordStore("myStore", false);
RecordEnumeration re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement()) {
    int recordId = re.nextRecordId();
    byte[] bytes = rs.getRecord(recordId);
    String data = new String(bytes);
    System.out.println(data);
}
rs.closeRecordStore();

JAR과 JAD로 배포되는 MIDlet Suite

MIDlet Suite는 클래스를 컴파일한 뒤 JAR로 패키징한다. 코드 보호와 크기 감소가 필요하면 ProGuard를 이용해 난독화할 수 있다.

# 클래스 컴파일
javac -bootclasspath midpapi.jar -d bin src/com/example/*.java

# JAR 패키징
jar cfm MyApp.jar META-INF/MANIFEST.MF -C bin .

JAD는 Java Application Descriptor로, MIDlet Suite의 메타데이터와 다운로드 전 정보를 제공한다. MIDP 2.0부터 선택적으로 사용할 수 있다.

MIDlet-Name: MyApp
MIDlet-Version: 1.0.0
MIDlet-Vendor: MyCompany
MIDlet-Description: My awesome MIDlet
MIDlet-Icon: /icon.png
MIDlet-Jar-URL: MyApp.jar
MIDlet-Jar-Size: 12345
MicroEdition-Profile: MIDP-2.0
MicroEdition-Configuration: CLDC-1.1

설치는 선택적인 JAD 파일 다운로드, 호환성 확인, JAR 다운로드, 서명·크기 검증, 설치, 실행 순으로 진행된다. 통신사 포털인 SKT·KT·LGU+, OEM 제조사 스토어, WAP(Wireless Application Protocol) 사이트를 통한 배포가 가능했고 Bluetooth와 적외선 전송도 채널로 사용됐다.

샌드박스는 제한된 API 접근, 파일 시스템 격리, 네트워크 제한을 제공한다. Trusted 도메인은 서명된 MIDlet에 광범위한 권한을 부여하고, Untrusted 도메인은 미서명 MIDlet에 제한된 권한을 적용한다. 네트워크 접근, 파일 읽기·쓰기, SMS 발송, 위치 정보는 권한 관리 대상이며, JAR Signing과 X.509 인증서, 신뢰 체인이 서명 체계를 구성한다.

개발패키징(JAR)JAD생성배포설치실행보안샌드박스권한서명

제한된 플랫폼에서 제공한 기능 범위

HttpConnection은 HTTP 통신을, Connector는 연결 생성을 담당한다.

HttpConnection conn = (HttpConnection) Connector.open("http://example.com/data");
conn.setRequestMethod(HttpConnection.GET);

int responseCode = conn.getResponseCode();
if (responseCode == HttpConnection.HTTP_OK) {
    InputStream is = conn.openInputStream();
    // 데이터 읽기
    is.close();
}
conn.close();

MIDP 2.0에서는 SocketConnection도 사용할 수 있다.

SocketConnection socket = (SocketConnection) Connector.open("socket://example.com:8080");
OutputStream os = socket.openOutputStream();
os.write("Hello".getBytes());
os.close();
socket.close();

MMAPI(Mobile Media API)는 사운드 재생과 카메라 같은 멀티미디어 기능을 다뤘다.

// 사운드 재생
Player player = Manager.createPlayer(getClass().getResourceAsStream("/sound.wav"), "audio/x-wav");
player.realize();
player.start();

// 카메라
Player camera = Manager.createPlayer("capture://video");
camera.realize();
VideoControl vc = (VideoControl) camera.getControl("VideoControl");

게임 API에서는 GameCanvas, Sprite, TiledLayer를 활용할 수 있다.

public class MyGame extends GameCanvas implements Runnable {
    public MyGame() {
        super(true);  // 더블 버퍼링
    }

    public void run() {
        Graphics g = getGraphics();
        while (running) {
            // 게임 로직
            // 렌더링
            g.setColor(255, 255, 255);
            g.fillRect(0, 0, getWidth(), getHeight());
            // 게임 객체 그리기
            flushGraphics();
            Thread.sleep(30);  // 약 33fps
        }
    }
}
// 스프라이트
Image image = Image.createImage("/sprite.png");
Sprite sprite = new Sprite(image, 32, 32);  // 32x32 프레임
sprite.setPosition(100, 100);

// 타일 레이어
Image tileImage = Image.createImage("/tiles.png");
TiledLayer tileLayer = new TiledLayer(10, 10, tileImage, 16, 16);

JSR-179 Location API는 위치 제공자에서 좌표를 조회하는 방식을 제공한다.

// 위치 제공자
LocationProvider provider = LocationProvider.getInstance(null);

// 위치 조회
Location location = provider.getLocation(60);  // 60초 타임아웃
Coordinates coords = location.getQualifiedCoordinates();
double latitude = coords.getLatitude();
double longitude = coords.getLongitude();

스마트폰 이전 플랫폼이 남긴 제약

MIDlet은 제한된 CPU와 메모리, 느린 그래픽 렌더링, 제한된 멀티태스킹, KVM(K Virtual Machine)의 낮은 성능이라는 제약 위에서 동작했다. 화면 크기와 해상도는 작았고, 터치스크린 없이 키패드 입력을 사용했으며, UI 컴포넌트도 단순했다. 디바이스별 구현 차이도 일관된 경험을 어렵게 했다.

제조사별 확장 API인 Nokia UI와 Samsung API는 플랫폼 파편화를 키웠다. 개발자는 디바이스별 테스트와 호환성 문제를 감수해야 했고, Write Once, Run Anywhere는 Write Once, Test Everywhere라는 표현으로도 남았다. Eclipse ME, NetBeans Mobility Pack 같은 제한된 IDE, 복잡한 디버깅, 불완전한 에뮬레이터, 복잡한 배포 절차도 개발 환경의 한계였다.

2007년 iPhone은 iOS 플랫폼, Objective-C/Swift, App Store, 터치 인터페이스, 강력한 하드웨어를 제시했다. 2008년 Android는 Google 주도의 Java 기반 Dalvik/ART VM, Google Play Store, 오픈소스 생태계를 바탕으로 빠르게 확산했다. 스마트폰 앱 생태계가 커지면서 더 나은 성능과 UX, 풍부한 개발 도구, 대규모 앱 스토어가 MIDlet을 대체했고 개발은 중단됐다.

피처폰 시장 축소와 함께 MIDlet은 레거시 시스템이 됐으며 일부 개발도상국에서만 제한적으로 사용된다. 그럼에도 Java의 모바일 진출, 플랫폼 독립성 실험, 모바일 개발 경험 축적의 출발점이었다. 플랫폼 파편화, 성능과 UX, 개발 도구, 빠른 혁신이 모바일 플랫폼에서 왜 중요한지 보여주는 사례이기도 하다.

모바일플랫폼MIDlet(2000년대)iOS(2007~)Android(2008~)피처폰시대스마트폰시대쇠퇴주류
MIDletJava MEMIDP피처폰모바일 애플리케이션