Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

CHƯƠNG 2

HƯỚNG TIẾP CẬN

Khi lập trình trên điện thoại di động, cần phải khai thác được các thế mạnh của điện thoại ngày nay là âm thanh và hình ảnh. Trong phần này chúng em sẽ trình bày một số kỹ thuật xử lý âm thanh, một số kỹ thuật xử lý đồ họa và kỹ thuật xử lý phím cũng sẽ được trình bày chi tiết. Tất cả các kỹ thuật này được thử nghiệm thành công trên các điện thoại của các hãng điện thoại lớn như Nokia, Samsung hay Sony Ericssion.

2.1 Xử lý hình ảnh đồ hoạ
2.1.1 Tìm hiểu các đối tượng đồ họa
Ngày nay với sự phát triển nhanh chóng của công nghệ di động, chiếc điện thoại di động không còn đơn giản như trước đây là có nhiệm vụ thoại mà còn là phương tiện rất hữu hiệu trong công việc và giải trí. Về hình ảnh, trước đây màn hình điện thoại di động chỉ là màn hình đen trắng nhưng ngày nay màn hình điện thoại là màn hình màu và ở một số loại điện thoại mới có đến vài trăm ngàn màu. Đó là những ưu điểm mà giúp điện thoại di động ngày càng thân thiết với người sử dụng hơn. Với những ưu điểm đó, vậy thì làm sao để xây dựng các ứng dụng giải trí trên điện thoại di động hiệu quả. Phần này sẽ trình bày những khía cạnh cơ bản giúp cho việc khai thác các điểm mạnh về đồ hoạ một cách hiệu quả hơn. Hầu hết các điện thoại di động ngày nay đều sử dụng công nghệ Java. Thế mạnh của ngôn ngữ này là có thể chạy được trên các platform khác nhau. Để xây dựng các ứng dụng giải trí mạnh về đồ hoạ, môi trường MIDP 1.0 cung cấp cho chúng ta hai mức đồ hoạ cơ bản là đồ họa mức thấp và đồ hoạ mức cao. Đồ hoạ mức cao dùng cho văn bản hay form. Đồ hoạ mức thấp dùng cho các ứng dụng trò chơi yêu cầu phải vẽ lên màn hình.

20

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Hình sau biểu diễn hai mức độ đồ hoạ.
Displayable

Mức thấp
Lớp Canvas

Mức cao
Lớp Screen

-

Các ứng dụng game Vẽ lên màn hình Các sự kiện nhấn phím
Ít tính khả chuyển

Textbox

List

Form

Alert

-

Các ứng dụng doanh nghiệp Có tính khả chuyển cao.
Không điều khiển các thành phần

Cả hai lớp đồ hoạ mức thấp và lớp đồ họa mức cao đều là con của lớp
Displayable. Trong MIDP chỉ có một lớp Displayable được hiển thị tại một

thời điểm, có thể định nghĩa nhiều màn hình nhưng chỉ hiển thị một màn hình.
Public abstract class Canvas extends Displayable Public abstract class Screen extends Displayable

- Đồ hoạ mức cao: là lớp con của lớp Screen. Nó cung cấp các thành phần như
Textbox, List, Form, Alert. Ta ít điều khiển việc sắp xếp các thành phần trên

màn hình. Việc sắp xếp phụ thuộc vào nhà sản xuất. Textbox: Cho phép người dùng nhập và soạn thảo văn bản. Ta có thể định nghĩa số ký tự tối đa, giới hạn loại dữ liệu nhập (số học, mật khẩu, email,…) và hiệu chỉnh nội dung textbox. Kích thước thật sự của textbox có thể nhỏ hơn yêu cầu khi chạy trong thực tế (do giới hạn của thiết bị). Kích thước thật sự của textbox có thể lấy bằng phương thức getMaxSize(). List: Chứa danh sách các lựa chọn, người dùng có thể tương tác với danh sách và chọn một hay nhiều item. Có 3 loại danh sách là: IMPLICIT, EXCLUSIVE và MULTIPLE. Alert: hiển thị một màn hình pop-up trong một khoảng thời gian, nó được dùng để cảnh báo hay thông báo lỗi.Thời gian hiển thị có thể đuợc thiết lập bởi ứng dụng

21

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

hoặc được thiết lập là FOREVER cho đến khi người dùng nhấn phím bất kỳ để đóng lại. Form: Cho phép hiển thị nhiều item khác nhau trên một màn hình. - Đồ hoạ mức thấp: là lớp con của lớp Canvas. Lớp này cung cấp các phương thức đồ họa cho phép vẽ lên màn hình cùng với các phương thức xử lý sự kiện bàn phím. Lớp này dùng cho các ứng dụng trò chơi cần điều khiển nhiều việc vẽ lên màn hình. Khi có một ứng dụng đòi hỏi đồ họa mạnh chắc chắn ứng dụng đó phải sử dụng các lớp này. Đây là những lớp đồ hoạ cấp thấp chạy được trên hầu hết các loại điện thoại di động hỗ trợ Java. Một số thử nghiệm việc ứng dụng các hàm đồ họa cấp thấp của MIDP 1.0 đề xây dựng các ứng dụng yêu cầu khả năng đồ hoạ cao cho thấy tốc độ thực hiện nhanh hơn khi sử dụng MIDP 2.0.

2.1.2 Kỹ thuật xử lý hoạt hình
Một trong những thành phần không kém phần quan trọng khi xây dựng các ứng dụng mạnh về đồ hoạ là tạo các hiệu ứng hoạt hình. Để làm được điều đó, MIDP 1.0 cung cấp cho ta các đối tượng Timer, Thread, Runnable cho phép thực hiện các hiệu ứng theo chu kỳ thời gian định trước.

2.1.2.1 Sử dụng đối tượng Timer đề tạo hoạt hình
Trong đồ án, các game sử dụng các lớp về đồ hoạ để biểu diễn các hình ảnh và sử dụng Timer cho việc làm chuyển động các đối tượng cũng như tính thời gian. Hoạt hình thực chất là sự luân phiên thay đổi các hình theo một khoảng thời gian xác định. Tuỳ vào số lượng hình ảnh và khoảng thời gian mà hiệu ứng sẽ diễn ra đẹp hay không đẹp. Mô tả kỹ thuật xử lý hoạt hình sử dụng đối tượng Timer.
Public class MainGame extends Canvas { Public MainGame(){ // Khởi tạo Timer tm = new Timer(); doAnimation tt = new doAnimation ();

22

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

tm.schedule(tt, 500,500); hình }

// Thiết lập thời gian hoạt

Protected void paint(Graphics g){ // Các thao tác vẽ hình ảnh } public class doAnimation extends TimerTask { public final void run(){ // Cập nhật hình ảnh repaint(); // vẽ lại màn hình sau khi cập nhật } } }

Theo khoảng thời gian xác định, hình ảnh sẽ được lớp doAnimation cập nhật mới và phương thức repaint() sẽ được gọi để cập nhật lại hình ảnh trên thiết bị. Phương thức repaint() sẽ gọi phương thức paint(Graphic g) để vẽ lại màn hình theo các tham số được cập nhật trong phương thức run() của lớp doAnamation.

2.1.2.2 Sử dụng Thread đề tạo hoạt hình
Một hướng khác khi tạo hiệu ứng hoạt hình là ta có thể dùng thread đề định thời gian. Thread được lớp java.lang.Thread cung cấp, nó có nhiều phương thức nhưng có phương thức quan trọng mà ta cần chú ý khi xây dựng ứng dụng là start() .
Start() – để khởi động thread

Kỹ thuật sử dụng thread được thực hiện như sau:
public class GameCanvas { private final int DELAY = 100; private GameThread thread; private boolean running; public GameCanvas() { } public void start() { running = true;

23

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

thread = new GameThread(); thread.start(); } public void stop() { running = false; } public class GameThread extends Thread { public void run() { while (running) { // Cập nhật hình ảnh // Kiểm tra các điều kiện chuyển động // vẽ lại hình ảnh try { Thread.sleep(DELAY); } catch (Exception ex) { } } } }

}

Cờ running cho biết thread có đang thực hiện hay không, thread sẽ dừng khi cờ này được gán bằng false khi gọi phương thức stop(). DELAY cho biết thời gian lặp lại hành động của thread.

2.1.2.3 Sử dụng giao diện Runnable đề tạo hoạt hình
Cách khác để tạo hoạt hình là sử dụng giao diện Runnable của lớp
java.lang.Runnable. Giao diện này được thiết kế để cung cấp những phương

thức thông dụng cho những đối tượng hoạt hình và nó được kế thừa từ lớp Thread. Giao diện Runnable có một phương thức duy nhất là run(). Khi một đối tuợng sử dụng giao diện Runnable đề tạo thread, phương thức run() được gọi để thực thi thread. Kỹ thuật sử dụng giao diện Runnable được cài đặt như sau:
public class GameCanvas implements Runnable { private final int DELAY = 100; private Thread thread; private boolean running; public GameCanvas() {

24

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

} public void start() { running = true; thread = new Thread(this); thread.start(); } public void stop() { running = false; } public void run() { while (running) { // Cập nhật hình ảnh // Kiểm tra tương thích // vẽ lại hình ảnh try { Thread.sleep(DELAY); } catch (Exception ex) { } } } }

Do nó được kế thừa từ Thread nên cũng có cờ running cho biết giao diện có đang thực thi hay không, và nó sẽ ngừng khi cờ running được thiết lập là false khi phương thức stop() được gọi. Khi lập trình xử lý hoạt hình, chúng ta nên cân nhắc giữa việc sử dụng Thread và sử dụng Timer. Thread không thích hợp cho việc tạo các hiệu ứng hoạt hình theo từng frame (frame-rate), cách tốt nhất đối với hoạt hình dạng này là ta dùng đối tượng Timer vì đối tượng này không sử dụng trực tiếp đồng hồ hệ thống nên việc thực hiện hoạt hình sẽ hiệu quả hơn. Timer dễ sử dụng hơn Thread nhưng do ban đầu Java chưa hỗ trợ lớp Timer nên có một số game sử dụng Thread đề thay thế.

2.2 Xử lý thao tác bàn phím
Do chúng ta xây dựng ứng dụng dựa trên giao diện lập trình cấp thấp nên ta có thể dễ dàng kiểm soát thao tác phím dựa trên đối tượng Canvas. Hầu hết các ứng dụng J2ME đều sử dụng Thread cho việc kiểm soát bàn phím. Khi người dùng nhấn

25

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

một phím trên điện thoại, một sự kiện được lập tức gửi cho ứng dụng, ứng dụng sẽ nhận sự kiện, phân tích phím nhấn và xử lý hành động tương ứng. Đối tượng Canvas cung cấp phương thức keyPressed (int keycode) để xử lý phím nhấn, đây là cách xử lý sự kiện bấm phím.
private int key; public void keyPressed(int keyCode) { // Xử lý hành động tương ứng switch (keyCode){ case -1: case 50: //Up or Number 2 // Hành động khi nhấn phím break; case -2: case 56: //down or Number 8 break; case -3: case 52: // Left or Number 4 break; case -4: case 54: // Nhấn phím số 6 hoặc phím Right break; case -5: case 53: // Nhấn phím 5 hoặc phím Select break; } repaint(); // Vẽ lại màn hình sau khi nhấn phím } public void keyReleased(int keyCode) { // Đặt = 0 khi không có phím nào đuợc nhấn. key = 0; }

Khi có sự kiện nhấn phím, hàm này sẽ kiểm tra và xử lý hành động tương ứng. Sau khi xử lý sẽ gọi phương thức repaint() để cập nhật lại màn hình thiết bị theo hành động mà người dùng vừa thực hiện.

26

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

2.3 Đánh giá ưu khuyết điểm của việc sử dụng đối tượng đồ hoạ cấp thấp
Ưu điểm: - Khai thác tối đa thế mạnh về đồ hoạ mà các loại điện thoại di động ngày nay cung cấp như màu sắc, hình ảnh, hoạt hình. - Cho phép can thiệp trực tiếp vào màn hình hiển thị của thiết bị, giúp dễ dàng xây dựng các ứng dụng hoạt hình. - Cho phép kiểm soát phím nhấn dễ dàng thông qua lớp Canvas. - Cho phép ứng dụng thực hiện với tốc độ nhanh vì thao tác trực tiếp trên màn hình thiết bị mà không qua đối tượng trung gian nào. Khuyết điểm: - Khó khăn cho việc xây dựng ứng dụng vì phải sử dụng đối tượng đồ hoạ cấp thấp. - Do tất cả các thao tác vẽ đều được lớp Canvas xử lý nên phải có giải pháp nhằm giúp cho lớp này thực hiện thao tác vẽ nhanh và hiệu quả nhất.

2.4 Một số giải pháp khi xử lý đồ họa
- Cải tiến tốc độ vẽ hình ảnh trên màn hình thiết bị: Do việc nạp ảnh từ file thường tốn nhiều thời gian nên ta không thể vừa thực hiện vẽ hình ảnh lên màn hình vừa đọc hình ảnh từ file, việc này sẽ làm hoạt hình bị giật. Do đó chúng ta cần phải load hình vào bộ nhớ máy trước khi thực hiện thao tác vẽ như sau: Load tất cả các hình ảnh cần thiết vào bộ nhớ máy trong phương thức constructor của đối tượng sử dụng hình ảnh một lần. Trong phương thức paint(), ta luân phiên vẽ các hình ảnh này theo khoảng thời gian xác định. Điều này sẽ giúp cho hoạt hình không bị giật, do hình ảnh đã được nạp vào trong bộ nhớ ngay từ đầu nhưng sẽ gây ra một khó khăn là tài nguyên hệ thống bị chiếm giữ.

27

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

- Vẽ những phần hình ảnh có thay đổi, không vẽ hết màn hình: Khi thực hiện các thao tác hoạt hình ta nên vẽ những phần hình ảnh bị thay đổi không nên vẽ lại toàn bộ màn hình. - Thu dọn tài nguyên hệ thống sau khi dùng: Khi tạo ra các đối tượng đồ hoạ như Image, hệ thống sẽ không tự động thu dọn tài nguyên mà các đối tượng này chiếm giữ cho đến khi kết thúc ứng dụng. Điều này sẽ làm tốn tài nguyên của hệ thống. J2ME cho phép chúng ta thu dọn tài nguyên hệ thống bằng tay bằng cách gọi
System.gc(). Nhưng trước khi gọi phương thức này các đối tượng bạn muốn thu

hồi tài nguyên phải được gán bằng null. Có một chú ý là ta nên giải phóng hết tài nguyên trước khi chuyển sang thực hiện thao tác kế tiếp.
void freeMemory() { img1 = null; img2 = null; img3 = null; System.gc(); //Call Garbage Collector }

Không nên thực hiện theo cách sau:
void loadData() { try { img1 = null; img1 = Image.createImage("img1.png"); img2 = null; img2 = Image.createImage("img2.png"); img3 = null; img3 = Image.createImage("img3.png"); } catch (...) {}

}

28

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Về mặt cú pháp thì không có gì sai, nhưng về mặt thực thi, do máy điện thoại có tốc độ thấp nên trình Garbage Collector không thể thực hiện việc xoá bộ nhớ ngay lập tức, nó cần một khoảng thời gian nhất định. Chính vì thế bạn nên dùng theo cách của method "freeMemory()".

2.5 Xử lý âm thanh trong ứng dụng
2.5.1 Vấn đề hỗ trợ âm thanh trên điện thoại di động.
Một khó khăn rất lớn khi lập trình các ứng dụng trên điện thoại di động sử dụng MIDP 1.0 là phần xử lý âm thanh. Do MIDP 1.0 không hỗ trợ việc xứ lý âm thanh nên các nhà sản xuất phải cung cấp thêm các hàm API chuyên dùng cho việc này. Đây là một cố gắng của nhà sản xuất để cải tiến chất lượng các điện thoại tuy nhiên nó lại gây khó khăn cho các lập trình viên khi xây dựng các ứng dụng. Do phải phụ thuộc vào các API của nhà sản xuất cung cấp nên tính chất “Viết một lần – Chạy trên nhiều nền“ không còn đúng nữa. Điều này đã được SUN cố gắng khắc phục trong phiên bản MIDP 2.0, nhưng hiện nay chỉ có một số ít điện thoại hỗ trợ phiên bản này. Trong phần này chúng em sẽ trình bày một số giải pháp khi xử lý âm thanh cho các ứng dụng trên điện thoại di động. Bảng mô tả một số API của một số nhà sản xuất điện thoại di động cung cấp cho việc xử lý âm thanh. STT 1 2 3 Nhà cung cấp Nokia Samsung Sony Ericssion Tên gói
com.nokia.mid.sound.*; com.samsung.util.AudioClip; javax.microedition.media.*; javax.microedition.media.control.*; của SUN) (gói

2.5.1.1 Đối với điện thoại Nokia:
Hiện nay Nokia có rất nhiều dòng sản phẩm nhưng ta có thể phân thành hai nhóm chính sau: nhóm thuộc Series 40 và nhóm thuộc series 60. Nhóm thuộc Series 40 gồm một số loại sau: 7210,6610, 7250, 6800, 3300,3200,3100,… sử dụng công

29

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

nghệ MIDP 1.0. Nhóm thuộc Series 60 như: 6600,7610, 3650, 3660,.. nhóm này sử dụng công nghệ MIDP 2.0. Do hiện nay số dòng sản phẩm hỗ trợ MIDP 2.0 tương đối ít nên chúng em viết ứng dụng dựa trên MIDP 1.0. Do MIDP 1.0 không hỗ trợ âm thanh nên để sử dụng được âm thanh, hãng Nokia cung cấp một số API chuyên dụng cho việc này. Các API mà nhà sản xuất Nokia cung cấp chỉ hỗ trợ cho việc phát các âm thanh dạng TONE, dạng WAV. TONE được lưu trữ theo dạng mảng các byte biểu diễn cho tần số Node nhạc như ví dụ sau:
byte[] dataIntro = { // Thiết lập thông số cho Tone (byte)0x02, (byte)0x4a, (byte)0x3a, (byte)0x40, (byte)0x04, (byte)0x00, (byte)0x19, (byte)0x2a, (byte)0xa2, (byte)0x34, (byte)0x49, (byte)0xaa, (byte)0x17, (byte)0xa1, (byte)0x6a, (byte)0x12, (byte)0xa1, (byte)0x1a, (byte)0x20, (byte)0xd5, (byte)0x0b, (byte)0x50, (byte)0x00 };

hoặc được lưu trữ trong file .ott (ví dụ amnhac.ott). Dạng WAV do kích thước quá lớn không phù hợp với việc lập trình trên điện thoại di động nên chúng em không trình bày trong phần này. Cách phát âm thanh dạng TONE:
private Sound introSnd=null; introSnd=new Sound(dataIntro, Sound.FORMAT_TONE); introSnd.setGain(254); public void playIntroduction(){ stopAllSound(); introSnd.play(1); } public void stopAllSound(){ introSnd.stop(); }

30

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Có một khó khăn khi sử dụng cách phát âm thanh dạng này là ta không thể phát song song nhiều hiệu ứng âm thanh cùng một lúc được. Đây là một trong những nhược điểm của MIDP 1.0, nó gây khó khăn cho việc tạo các hiệu ứng âm thanh cho ứng dụng. Hiện nay các loại Nokia sử dụng MIDP 1.0 không cung cấp các API phục vụ cho việc chơi các tập tin MIDI mặc dù là một số điện thoại Nokia đã hỗ trợ chơi nhạc MIDI. Cách kiểm tra điện thoại có hỗ trợ chơi âm thanh không: dùng phương thức className để tìm gói com.nokia.mid.soung.Sound, nếu tìm thấy tức là điện thoại có hỗ trợ chơi âm thanh.
private static TonePlayer makeTonePlayer() { TonePlayer player; try { Class.forName("com.nokia.mid.sound.Sound"); // Tạo lớp chơi âm thanh

Class class = Class.forName("example.tones.NokiaTonePlayer");

player = (TonePlayer)(clas.newInstance()); } catch (Exception e) { System.out.print(“ Không hỗ trợ âm thanh.”); } return player; }

Lớp chơi âm thanh trên Nokia.
Package example.tones; import com.nokia.mid.sound.*; // import gói chơi âm thanh class NokiaTonePlayer extends TonePlayer { private final Sound sound; NokiaTonePlayer() { sound = new Sound(0, 1L); } void play(int frequency) { sound.init(frequency, 2000L); sound.play(1); } void stop() { sound.stop(); } }

31

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Vì không thể phát cùng lúc nhiều kênh âm thanh nên trong chỉ chọn một trong hai cách sau: một là sử dụng âm thanh cho các hiệu ứng, hai là sử dụng âm thanh làm nhạc nên mà không có âm thanh cho phần hiệu ứng.

2.5.1.2 Đối với điện thoại SAMSUNG:
Các dòng sản phẩm của SAMSUNG sử dụng MIDP 1.0 và MIDP 2.0 điều được cung cấp lớp com.samsung.util.AudioClip để lập trình âm thanh. Hiện nay điện thoại SAMSUNG chỉ hỗ trợ âm thanh dạng .mmf (TYPE_MMF=1). Để tạo đối tượng AudioClip, ta dùng Constructor sau:
Public AudioClip(int type, String filename) throws java.io.IOException

Trong đó type luôn luôn bằng 1 (kiểu định dạng .mmf) và filename là tên tập tin mmf. Tập tin mmf thật chất là tập tin MIDI đặc biệt được SAMSUNG sử dụng cho dòng sản phầm của họ. Để kiểm tra điện thoại có hỗ trợ âm thanh không, ta dùng phương thức tĩnh
AudioClip.isSupported(). AudioClip còn hỗ trợ 4 phương thức điều khiển là play() để chơi nhạc, stop() dừng nhạc, pause() tạm dừng và resume() chơi tiếp

đoạn nhạc đã tạm dừng trước đó. SAMSUNG cũng tương tự như Nokia là ở mỗi thời điểm chỉ có một đối tượng AudioClip chơi nhạc và đó là đối tượng được gọi là play() sau cùng. Cách chơi nhạc được SAMSUNG xây dựng như sau:
import com.samsung.util.AudioClip; private AudioClip audioClip=null; public void playAudioClip(String filename){ stopAudioClip(); try{ audioClip=new AudioClip(1,filename); // tạo lớp AudioClip audioClip.play(10,1); // Play 10 lần, Loop time = 1 } catch (Exception e){ e.printStackTrace(); } } public void stopAudioClip(){ if (audioClip!=null){ try{ audioClip.stop();

32

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

audioClip=null; }catch (Exception e){ e.printStackTrace(); } } } public void pauseAudioClip(){ if (audioClip != null) { try { audioClip.pause(); } catch (Exception e) { e.printStackTrace(); } } } public void resumeAudioClip(){ if (audioClip!=null){ try{ audioClip.resume(); }catch (Exception e){ e.printStackTrace(); } } }

2.5.1.3 Đối với điện thoại SONY ERICSSION:
Với điện thoại Ericssion thì việc lập trình âm thanh tương đối dễ dàng hơn các loại Nokia hay SAMSUNG. Do các loại điện thoại Ericssion mà hỗ trợ Java điều sử dụng gói API về âm thanh của SUN. Gói này hỗ trợ chơi rất nhiều loại âm thanh được mô tả trong bảng sau:
1. 2. 3. 4. 5. Wave audio files: audio/x-wav AU audio files: audio/basic MP3 audio files: audio/mpeg MIDI files: audio/midi Tone sequences: audio/x-tone-seq

Khác với các loại điện thoại khác, Ericssion có thể chơi cùng lúc nhiều âm thanh khác nhau như có thể vừa chơi WAV vừa chơi TONE, MIDI và TONE. Ericssion đã cố gắng tận dụng lợi thế này để làm cho các sản phẩm của mình ngày càng thân thiện với người dùng hơn. Ericssion cung cấp lớp Manager dùng để quản

33

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

lý tài nguyên cho việc chơi âm thanh của đối tượng Player. Đối tượng Player cho phép thực hiện và kiểm soát quá trình chơi nhạc. Ví dụ sau minh họa cách dùng đối tượng Player và lớp Manager để chơi tone sequence.
try { Player p = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR); p.realize(); ToneControl tc = (ToneControl)p.getControl("ToneControl"); tc.setSequence(mySequence); p.start(); } catch (IOException ioe) { } catch (MediaException me) {}

Do không có nhiều thời gian tìm hiểu nên chúng em chỉ tìm hiểu được 3 loại điện thoại là Nokia, SAMSUNG và SONY Ericssion. Một số khác như Motorola, LG, Siemen nếu có điều kiện chúng em sẽ tìm hiểu sau.

2.5.2 Những khó khăn và hướng giải quyết khi xử lý âm thanh
Ta thấy rằng qua tìm hiểu ba loại điện thoại khác nhau là Nokia, Samsung, Ericssion sử dụng MIDP 1.0, cách lập trình xử lý âm thanh là khác nhau. Một số khó khăn mà chúng ta thấy khi lập trình âm thanh cho các loại điện thoại di động là: MIDP 1.0 không hỗ trợ âm thanh nên không có một chuẩn cụ thể nào cho việc lập trình âm thanh trên các dòng điện thoại di động khác nhau. Mỗi nhà sản xuất điều cung cấp các API riêng cho từng sản phẩm của họ. Âm thanh mà mỗi nhà sản xuất dùng cho điện thoại của họ cũng khác nhau. Vậy làm sao để khi viết một ứng dụng có thể chạy được trên các dòng sản phẩm khác mà không cần phải viết nhiều lần cho nhiều dòng sản phẩm khác nhau ? Cách giải quyết như sau:

34

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

-

Tạo lớp trừu tượng cho việc xử lý âm thanh chung, gồm các phương thức chính để có thể chơi nhạc. Tạo các lớp chơi âm thanh ứng với các dòng sản phẩm tương ứng kế thừa từ lớp trừu tượng. Dùng className đề tìm một số lớp đặc trưng của từng sản phẩm, nếu tìm thấy thì xác định được sản phẩm thuộc loại nào và dùng lớp tương ứng để chơi nhạc.

Cài đặt chi tiết cách xử lý âm thanh như sau:
public class AbSound {// Lớp Abstract cho việc chơi âm thanh public AbSound(){} public void StopSound(){} public void PlaySound(int i, int j, int k){} public synchronized void sound(byte type){} public void a(int i){} public void Init(){} public void Vibration(){} public void Light(){} }

Lớp âm thanh cho Nokia.
import com.nokia.mid.sound.*; import java.io.*; import com.nokia.mid.ui.DeviceControl; public class soundnokia extends AbSound implements SoundListener{ // Khai báo các biến âm thanh // Cài đặt tất cả các phương thức trừu tượng public void StopSound(){ // Code của Nokia } public void PlaySound(int i, int j, int k){ // Code của Nokia } public synchronized void sound(byte type){ // Code của Nokia }

}

35

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Lớp âm thanh cho SAMSUNG:
import com.samsung.util.*; public class SoundSS extends AbSound { // Khai báo các biến âm thanh // Cài đặt tất cả các phương thức trừu tượng public void PlaySound(int j, int k, int l){ // Code play nhạc của Samsung } public void StopSound(){ // Code Stop nhạc của Samsung } public synchronized void sound(byte type){ // Code của Samsung } public void Vibration(){ // Code của Samsung } public void Light(){ // Code của Samsung } }

Tương tự cho các máy khác ta cũng cài đặt như vậy. Sau đó để sử dụng ta cần có lớp Detect Sound như sau:
public class DetectSound{ public DetectSound(String s, String s1){} public static AbSound Init(){ AbSound q1; try{ Class.forName("com.nokia.mid.sound.Sound"); Class.forName("com.nokia.mid.sound.SoundListener"); Class.forName("com.nokia.mid.ui.DeviceControl"); Class class1 = Class.forName("soundnokia"); q1 = (AbSound)class1.newInstance(); } catch(Exception exception){ try{ Class.forName("com.samsung.util.AudioClip"); Class.forName("com.samsung.util.Vibration"); Class.forName("com.samsung.util.LCDLight"); Class class2 = Class.forName("SoundSS");

36

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

q1 = (AbSound)class2.newInstance(); }catch(Exception exception1){ q1 = new AbSound(); } } return q1; } }

Trong ứng dụng, khi cần sử dụng âm thanh ta chỉ cần gọi phương thức Init() để xác định xem loại điện thoại nào đang được sử dụng.
AbSound ss=DetectSound.Init();

2.5.3 Ứng dụng vào đồ án
Âm thanh trong ứng dụng được cài đặt cho hai loại điện thoại là Nokia và Samsung. Âm thanh được sử dụng cho phần nhạc nên của các trò chơi, không cài đặt âm thanh cho phần hiệu ứng do không phát âm thanh song song được. Trên Nokia âm thanh có phần đơn điệu hơn trên Samsung do trên Nokia sử dụng âm thanh dạng Tone còn trên Samsung sử dụng âm thanh dạng Midi (tập tin .mmf). Trong đồ án chúng em sử dụng kỹ thuật tìm lớp này để xây dựng ứng dụng tương thích với mọi loại điện thoại, ở đây chúng em chỉ cài đặt âm thanh cho hai loại điện thoại là Nokai và SAMSUNG.

2.6 Xử lý lưu trữ dữ liệu
2.6.1 Giới thiệu hệ thống lưu trữ dữ liệu trên điện thoại di động
Việc lưu trữ thông tin cho việc khởi tạo của các ứng dụng là cần thiết và quan trọng. Thông tin mà các ứng dụng có thể lưu trữ như các thông tin cấu hình ứng dụng, thông tin quá trình sử dụng các ứng dụng,…Đối với máy PC thì việc này tương đối dễ dàng khi chúng ta có trong tay nhiều thiết bị lưu trữ như HDD, CDROM, USB DISK,…nhưng việc này tương đối khó khăn đối với các ứng dụng trên điện thoại di động. MIDP cung cấp cho chúng ta một đối tượng lưu trữ là Record Management System (RMS) cho phép chúng ta lưu trữ thông tin dưới dạng các

37

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

record.

Các

API

của

đối

tượng

này

được

cung

cấp

trong

gói

javax.microedition.rms. Một vùng nhớ trên các thiết bị sử dụng MIDP sẽ được

dành riêng cho việc lưu trữ các dữ liệu của ứng dụng MIDlet. Vị trí và kích thước của vùng lưu trữ này được xác định tùy thuộc vào những thiết bị cụ thể. RMS cho phép lưu trữ dữ liệu khi ứng dụng thoát, khởi động lại và khi thiết bị di động tắt hay thay pin. Dữ liệu của ứng dụng sẽ tồn tại trên thiết bị di động cho đến khi ứng dụng thật sự được xóa khỏi thiết bị di động. Khi một MIDlet bị xóa, tất cả các record mà nó lưu trữ cũng bị xóa. Hình sau minh hoạ việc lưu trữ dữ liệu

MIDlet 1

MIDlet 1

MIDlet 3

Lưu trữ bản ghi 1

Lưu trữ bản ghi 2

Lưu trữ bản ghi 3

Như trong hình vẽ, các MIDlet có thể có nhiều hơn một tập hợp các record và chúng có thể truy xuất đến tập hợp dữ liệu lưu trong bộ MIDlet mà chúng lưu trữ. Do đó, MIDlet 1 và MIDlet 2 có thể truy truy xuất đến Record Store 1 và Record Store 2 nhưng chúng không thể truy xuất đến Record Store 3. Ngược lại, MIDlet 3 chỉ có thể truy xuất Record Store 3 mà không thể truy xuất đến Record Store 1 và Record Store 2. Tên của bộ lưu trữ dữ liệu phải là duy nhất trong một bộ MIDlet nhưng trong các bộ MIDlet khác nhau có thể trùng trên. Hệ thống RMS lưu trữ các bản ghi theo một mảng các byte. Các mảng byte có chiều dài khác nhau và mỗi mảng byte được gán một số ID bản ghi. Các bản ghi được định danh bằng một số ID duy nhất. Các số ID được đánh số bắt đầu từ 1. Các

38

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

số ID sẽ tăng dần và không được dùng lại cho dù bản ghi đó có bị xoá nên sẽ tồn tại một số khoảng trống trong các ID bản ghi. MIDP không kiểm soát việc ghi quá số bản ghi tối đa, điều này phụ thuộc vào ứng dụng.

2.6.2 Định dạng, thêm, xóa các record
Thêm record gồm 2 bước: bước đầu tiên là định dạng record theo các yêu cầu và bước hai là thêm các record đã được định dạng vào RMS. RMS không hỗ trợ sự tuần tự hoá do đó ta phải định dạng các mảng byte để lưu trữ bản ghi. Sau đây là ví dụ của việc định dạng lưu trữ bản ghi, mở một RMS và sau đó là thêm dữ liệu bản ghi vào RMS.
ByteArrayOutputStream baos=new ByteArrayOutputStream(); DataOutoutStream outputStream=new DataOutputStream(baos); outputStream.writeByte(‘T’); outputStream.writeInt(score); outputStream.writeUTF(name); byte[] theRecord=baos.toByteArray(); recordStore rs=null; rs=RecordStore.openRecordStore(“RecordStoreName”,CreateIfNotEx ist); int RecordID=rs.addRecord(theRecord,0,theRecord.length);

Record ID 1

Byte

Byte

Byte

Byte

Byte

Record ID 2

Byte

Byte

Byte

Byte

Byte

Byte

Byte

Byte

Byte

T

Score

Name

2.6.2.1 Định dạng dữ liệu bản ghi
Trong ví dụ trên hai dòng đầu tạo một luồng xuất để ghi dữ liệu bản ghi. Sử dụng đối tượng DataOutputStream cho phép các bản ghi dễ dàng được định dạng

39

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

theo các kiểu chuẩn của Java (long, int ,string,…) mà không phải quan tâm đến việc tách nó thành dữ liệu dạng byte. Phương thức writeInt(),
writeByte(), writeUTF() định dạng dữ liệu như hình vẽ (tag, score, name). Sử dụng thẻ tag làm có ích để xác định loại record sau này. Phương thức toByteArray() chép dữ liệu trong một luồng xuất thành một mảng byte chứa các

bản ghi để lưu trữ. Biến theRecord tham chiếu đến dữ liệu động đã được định dạng.

2.6.2.2 Thêm record và RMS
Khi dữ liệu đã được định dạng, nó có thể được thêm vào RMS. Phương thức
openRecordStore() tạo và mở một RMS với tên là RecordStoreName. Phương

thức addRecord dùng để thêm record bắt đầu từ byte 0 của theRecord và trả về ID của bản ghi gắn với record này.

2.6.2.3 Xóa bản ghi
Bản ghi được xóa bằng cách chuyển số ID bản ghi cho phương thức
deleteRecord() của đối tượng RecordStore. Khi bản ghi có ID 7 được xóa, thêm

mới bản ghi thì bản ghi đó có ID là 8, ID 7 không được dùng lại.

2.6.2.4 Lọc các bản ghi (Filtering Records)
Giao diện RecordFilter cung cấp một cách thuận tiện để lọc các bản ghi theo tiêu chuẩn nhất định, RecordEnumeration có thể được dùng để duyệt qua các bản ghi và chỉ trả về các record phù hợp với tiêu chuẩn xác định. Giao diện
RecordFilter có phương thức matches() dùng để xác định các tiêu chuẩn phù

hợp. Phương thức matches() có tham số đầu vào là mảng byte biểu diễn một bản ghi. Phương thức trả về true nếu bản ghi này phù hợp với tiêu chuẩn đã định nghĩa.

40

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Record ID Record ID Record ID Record ID

T S T S

Byte Byte Byte Byte

Byte Byte Byte Byte

Byte Byte Byte Byte

Byte Byte Byte Byte

Byte Byte Byte Byte

Byte Byte Byte Byte

Byte Byte Byte Byte

Byte Byte Byte Byte

Class IntegerFilter implements RecordFilter { Public boolean matches(byte [] candidate) throws IlleegalArgumentException { Return (candidate[0]==’T’); }

Trong ví dụ trên, IntegerFilter dùng để lọc ra các bản ghi có byte đầu tiên là T. Nên nhớ rằng các bản ghi không có cùng định dạng nên việc sử dụng byte đầu tiên làm thẻ tag là rất có ích. Phương thức matches chỉ trả về true nếu byte đầu tiên là T.

2.6.2.5 Sắp xếp các bản ghi
Các bản ghi trong RMS có thể được sắp xếp theo một thứ tự nào đó do chúng ta định nghĩa. Việc sắp xếp được thực hiện thông qua giao diện
RecordComparator. Giao diện này có phương thức compare() phải được implement để định nghĩa cách hai bản ghi so sánh theo thứ tự. Các tham số đầu vào

là hai mảng byte biểu diễn hai record. Phương thức compare() trả về một trong ba giá trị sau:
EQUIVALENT FOLLOWS PRECEDES

Hai record được xem là giống nhau. Record đầu tiên có thứ tự theo sau record thứ hai. Bản ghi đầu tiên có thứ tự đứng trước bản ghi thứ hai.

41

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

Ví dụ sắp xếp các record sử dụng giao diện RecordComparator.
Class IntegerCompare implements RecordComperation { Public int compare(byte[] b1, byte[] b2){ DataInputStream ByteArrayInputStream(b1)); DataInputStream ByteArrayInputStream(b2)); is1.skip(1); is2.skip(1); int i1=is1.readInt(); int i2=is2.readInt(); if (i1>i2) return RecordComperation.FOLLOWS; if (i1<i2) return RecordComperation.PRECEDES; return RecordComperation.EQUIVALENT; } } is2=new DataInputStream(new is1=new DataInputStream(new

Trong ví dụ trên, tham số b1, b2 biểu diễn hai bản ghi được chuyển cho phương thức compare(). Sử dụng phương thức DataInputStream() cho phép sử dụng các kiểu dữ liệu chính của Java (int, String, long,…) thay vì thao tác trực tiếp với dữ liệu byte. Phương thức skip() bỏ qua byte đầu tiên trong mỗi luồng. Phương thức readInt() đọc số nguyên trong mỗi luồng nhập. Dòng cuối so sánh các giá trị số nguyên và trả về một trong các kết quả là FOLLOWS, PRECEDES, EQUIVALENT. Như vậy thứ tự sắp xếp các record được xác định bởi giá trị các số nguyên.

2.6.2.6 Liệt kê các bản ghi
Việc liệt kê các bản ghi lưu trữ trong RMS được thực hiện bằng cách dùng giao diện RecordEnumeration kết hợp với các lớp RecordFilter và
RecordComparator. Lớp RecordEnumeration giữ thứ tự luận lý của các record,

lớp RecordFilter xác định các tập con từ RMS sẽ được sắp xếp,
RecordComparation định nghĩa thứ tự sắp xếp của các record. Nếu không dùng RecordFilter thì tất cả các record được lưu trữ trong RMS sẽ được sử dụng. Nếu RecordComparation không được dùng thì tất cả các record được trả về theo thứ tự

42

Đồ án tốt nghiệp

Ứng dụng Java cho điện thoại di động

ngẫu nhiên. Các record được duyệt qua bằng phương thức nextRecord(). Lần đầu tiên được gọi nó sẽ trả về record đầu tiên trong tập liệt kê. Lần gọi kế tiếp nó sẽ trả về record kế tiếp theo thứ tự được ta định nghĩa. Ví dụ biểu diễn quá trình liệt kê các record.
IntegerFilter iFilt= new IntegerFilter(); IntegerCompare iCompare= new IntegerCompare(); RecordEnumeration intRecEnum=null; intRecEnum=recordStore.enumerateRecords((RecordFilter)iFilt, (RecordCompare)iCompare,false); while (intRecEnum.hasNextElement()){ byte b[]=intRecEnum.nextRecord(); }

Trong ví dụ trên, một đối tượng IntegerFilter và IntegerCompare được tạo ra. IntegerFilter sẽ chỉ trả về các record chứa field số nguyên,
IntegerCompare sẽ sắp xếp các record theo thứ tự số học.

Bộ liệt kê bản ghi sẽ được định nghĩa và khởi tạo bằng output của phương thức
enumerateRecords() của lớp RecordStore. Phương thức enumerateRecords()

có 3 tham số: tham số đầu tiên là đối tượng lọc (iFilt), tham số thứ hai tham chiếu đến đối tượng sắp xếp (iCompare), tham số cuối cùng là một giá trị boolean xác định bộ liệt kê có được cập nhật khi các record được cập nhật, thay đổi hay xóa không.

43