Có 2 vấn đề quan trọng khi phát triển ứng dụng trên J2ME, đó là:

  • Hiệu suất(tốc độ thực thi)
  • Kích thước

1.Hiệu suất

Quy tắc quan trọng về cải thiệt hiệu suất là “làm cho nó thật đơn giãn (keep it simple)”, đừng cố gắng làm cho hệ thống trở nên phức tạp, trên chiếc điện thoại, ta phải nhớ rằng mọi người muốn ứng dụng chạy nhanh và dễ dàng sử dụng.

1.1.Thread

  • Trong một ứng dụng không nên tạo ra quá nhiều thread bởi sẽ có rất nhiều thiết bị không xử lý được nhiều thread và chúng sẽ bị treo. Kết nối mạng là trường hợp ngoại lệ để tạo ra một Thread mới.
  • Giảm thiểu việc sử dụng  đồng bộ, có cơ chế quản lý các đoạn code đồng bộ một cách hợp lý, tránh rơi vào tình trạng Deadlock. Đồng bộ trên phương thức paint() và keyPressed() có thể chấp nhận được.
  • Tránh sử dụng lớp Timer bởi nó cũng là một thread mở rộng.
  • Tạo một Thread bacckground trong phương thức startApp() và tái sử dụng nó.
  • Không sử dụng phương thức Display.callSerially(), bởi nó rất chậm và gây ra nhiều lỗi trên các thiết bị.
  • Tránh gọi phương thức serviceRepaints() trên các thiết bị legacy khi hiệu suất là một vấn đề lớn đối với chúng.
  • Cần đảm bảo thread-safe(background thread và system thread) nếu phương thức serviceRepaints() không được gọi.

Ví dụ:

public void startApp() {
animationThread = new Thread(this);
}

public void run() {
init();
while(!exitApp) {
updateModel(); // your app logic
repaint();
serviceRepaint();
sleep(50); // may choke slow devices if too small
}
}

1.2.System Callbacks

Trong quá trình xử lý, bạn cần để ý đến System Callbacks, chúng được gọi bởi system thread. Chúng không được block và phải trả về ngay khi có thể để tránh làm chậm VM, một bất trắc có thể xảy ra nếu không trả về nhanh chóng. Đây là danh sách các phương thức có thể được gọi system callbacks:

·         paint
·         keyPressed
·         startApp/pauseApp
·         hideNotify/showNotify
·         MIDlet constructor

Ví dụ: dưới đây là một đoạn code nên tránh

public void paint(Graphics g) {
  updateModel();
  drawBackground(g);
  drawForeground(g);
}
 
public void run() {
  while(!exitApp) {
    repaint();
    serviceRepaints();
    sleep(50);
  }
}

Ở ví dụ trên, ta cập nhập mô hình(model), một hoạt động tốn kém được gọi trong phương thức paint(). Chúng ta nên thay đổi lại như sau:

public void paint(Graphics g) {
  drawBackground(g);
  drawForeground(g);
}
 
public void run() {
  while(!exitApp) {
    updateModel(); // better do it here
    repaint();
    serviceRepaints();
    sleep(50);
  }
}

1.3.Các thiết bị khác nhau – hiệu suất khác nhau

Có sự khác biệt rất lớn giữa các thiết bị cấp cao và cấp thấp, chúng có thể chạy nhanh hơn hay chậm hơn bạn mong muốn, vì thế bạn không thể đoán trước được hiệu suất thực thi. Cách tốt nhất là bạn nên đưa vào hệ thống thời gian thức như ví dụ dưới đây:

public void updateModel() {
  curTime = System.currentTimeMillis();
  elapsedTime = curTime – prevTime;
  // using elapsedTime for your app logic here
  prevTime = curTime;
}
 
public void run() {
  while(!exitApp) {
    updateModel();
    repaint();
    serviceRepaints();
    sleep(50);
  }
}

1.4.I/O

Việc sử dụng các lời gọi liên quan đến RMS và getResourceAsStream là rất chậm. Hãy xem xét khi sử dụng chúng.

1.4.1.RMS

  • Đọc toàn bộ bản ghi vào một bộ đệm buffer.
  • Sau đó parse buffer.
  • Tiếp tục ghi vào buffer đơn, rùi ghi buffer đó vào một bản ghi.

1.4.2getResourceAsStream()

  • Rất chậm trên một số thiết bị( 20 byte trên một giây).
  • Việc tải lớp nhanh hơn trên các thiết bị này.
  • Lưu trữ dữ liệu trong các file class riêng biệt thay vì sử dụng tài nguyên. Điều này có thể dẫn đến thời gian nạp ứng dụng lâu hơn.

1.5.Các vấn đề chung

Sau đây là một số vấn đề nhỏ để có thể tạo ra sự khác biệt lớn:

  • Tránh tạo ra các đối tượng không cần thiết.
  • Giảm thiểu, sử dụng lại vòng đời của các instance đối tượng mà bạn sử dụng,
  • Sử dụng StringBuffer để thay thế cho toán tử “+” khi xử lý String.
  • Các Image càng nhỏ càng tốt, tách các image lớn thành các image nhỏ, tạo ra một vùng đệm để chứa image.
  • Nhóm các đối tượng(object pooling) có thể làm việc trong một số trường hợp.
  • Trong các vòng lặp, tránh tạo ra các biến xử lý không cần thiết.
  • Sử dụng “switch-case” để thay thế cho block “if”, bởi chúng được dịch sang byte code java nhanh hơn.
  • Sử dụng các biến kiểu public một cách trực tiếp thay vì sử dụng các phương thức setter/getter.
  • Gán giá trị cho các biến bằng null khi không còn dùng đến chúng nữa.
  • Garbage Collector gọi rõ ràng và tường minh trên một số thiết bị.
  • Sử dụng biến cục bộ thay vì biến toàn cục nếu có thể bởi các biến địa phương nhanh hơn và chiếm ít bytecode hơn.

Hãy xem xét ví dụ sau:

for(y = 0; y < oriHeight; y++) {
  for(x = 0; x < oriWidth / 2; x++) {
    int idx1 = y * oriWidth + x;
    int idx2 = (y + 1) * oriWidth – 1 - x;
    curPixel = buf[idx1];
    buf[idx1] = buf[idx2];
    buf[idx2] = curPixel;
  }
}

Nếu chúng ta có thể tạo các biến chỉ số bên ngoài vòng lặp như:

  int idx1;
  int idx2;
  for(y = 0; y < oriHeight; y++) {
    idx1 = y * oriWidth;
    idx2 = (y + 1) * oriWidth – 1;
    for(x = 0; x < oriWidth / 2; x++) {
      curPixel = buf[idx1];
      buf[idx1] = buf[idx2];
      buf[idx2] = curPixel;
      idx1++;
      idx2--;
    }
  }

thì có thể tiết kiệm được vài giây trên thiết bị thật.

2.Kích thước

Những hạn chế kích thước ứng dụng J2ME đến từ hai nguồn:

  • Khả năng(capability) thiết bị, một số thiết bị chỉ chấp nhận các ứng dụng 64 kb!!
  • Hạn chế gateway của nhà điều hành(operator), để tránh khách hàng phải chờ đợi quá lâu (và thanh toán nhiều tiền) để tải về một ứng dụng, một số nhà điều hành giới hạn kích thước tối đa của ứng dụng(thông thường khoảng 300kb).

Kích thước file jar của thiết bị đã được cải thiện liên tục theo thời gian, nhưng nó không bao giờ là đủ cho các nhà phát triển! Vì vậy, ở đây một số lời khuyên để giúp bạn

  • Giảm thiểu số lượng các lớp, tránh OOP (trong thực tế), mỗi lớp/giao diện đóng góp ít nhất là 250 byte sau khi nén.
  • Sử dụng một Obfuscator.
  • Tối ưu hóa hình ảnh.
  • Thay đổi thuật toán ZIP, các tiện ích của JDK JAR không phải là tối ưu.

2.1.Obfuscator

Mục đích của việc sử dụng Obfuscator nhằm để:

  • Loại bỏ các package.
  • Đổi tên phương thức, tên trường sao cho ngắn nhất.
  • Hủy bỏ những đoạn code không sử dụng.

Hai phần mềm mã nguồn mở thông dụng nhất hiện nay gồm: Proguard và Retroguard.

2.2.Image

Các vấn đề về image thường mắc phải:

  • Không nén.
  • Mỗi file PNG có chứa  nhiều hơn một header và footer.
  • Lộn ngược(flip) hay chuyển đổi(transform) hình ảnh trên thiết bị MIDP 2.0 hoặc là rất chậm hoặc là không làm việc. Hoặc không hỗ trợ trên thiết bị MIDP 1.0.

Sau đây là một số giải pháp:

  • Sử dụng các trình tối ưu file PNG như: OptiPNG hay PNGCRUSH.
  • Không nén các nội dung hình ảnh.
  • Kết hợp nhiều file PNG vào cùng một tài nguyên duy nhất hay một hình ảnh lớn để hạn chế header và footer.
  • Lộn ngược(flip) hình ảnh.
  • Tư động flip hình ảnh ở thời gian chạy.
  • Tùy thuộc vào mục đích của ứng dụng để có sự cân bằng về hiệu suất/kích thước file jar.
  • Giảm độ sâu màu sắc nếu có thể.

Link tham khảo: http://wiki.forum.nokia.com/index.php/How_to_optimize_in_Java_ME

Bạn thấy bài viết này thế nào?