项目简介
基于MIDP1.0实现的个人通信录是我在学习MIDP子系统Record Management System的时候自己编写的应用程序,整个应用程序涉及到MIDP的高级和低级API、应用MVC实现界面导航、RMS的高级应用、多线程等知识。是学习J2ME开发不错的范例。由于本站有较多的文章介绍RMS,因此本文对开发中的部分问题进行了介绍。如果您有兴趣,可以直接下载源文件研究代码。如果提供下载请注明作者和出处
作者简介:
詹建飞(mingjava),北京邮电大学信息工程学院信号与信息处理专业研究生。
电子信箱:eric.zhan@263.net
本文将向大家讲述如何给予MIDP1.0实现手机通信录,读者需要具备J2ME的基本知识,了解它的构架和主要内容。开发工具选择了eclipse+wtk2.1+j2sdk1.4.2+eclipseME。
关于开发环境请参考搭建J2ME开发环境
关于J2ME的体系结构请参考J2ME平台的体系结构
精通MIDP用户界面设计
个人通信录提供了添加记录、浏览记录、删除记录、删除电话本、查找记录等功能。图4是几个主要界面的截图。细心的读者可能发现这里没有提供编辑的功能,读者可以免费得到个人通信录的源代码,这样您可以尝试添加这项功能。多读代码、多写代码是提高水平、掌握知识最快捷的途径。
在MIDP1.0中的javax.microedition.lcdui包内定义了21个类和3个接口,这比J2SE中的AWT和SWING要简单很多。在这24个类中,Display是负责设备的显示以及输入的管理器,通常我们通过调用setCurrent(Displayable displayable)方法来把displayable组件显示在手机屏幕上。Displayable代表了能够在屏幕上显示的组件对象,它的两个抽象子类是Canvas和Screen,他们分别代表了MIDP中的低级用户界面和高级用户界面。
Form,Alert,List和TextBox都是从Screen继承过来的,他们构成了MIDP中的高级用户界面。要清楚他们每个组件都必须单独占用一个屏幕,不能与其他组件放在一起。Form类在javax.microedition.lcdui包中至关重要,它是Item的容器,通过调用append(Item item)方法,你可以把TextField、DateField等Item放在Form内。例如下面的代码:
public NewPhoneUI(UIController uicontroller)
{
super(Title.add_record);
this.uicontroller = uicontroller;
nameField = new TextField(Title.name, null, 25, TextField.ANY);
mobileField = new TextField(Title.mobile, null, 25,
TextField.PHONENUMBER);
choice = new ChoiceGroup(null, ChoiceGroup.MULTIPLE);
phoneField=new TextField(Title.phone,null,25,TextField.PHONENUMBER);
emailField=new
TextField(Title.email, null, 25, TextField.EMAILADDR);
choice.append(Title.detail, null);
this.append(nameField);
this.append(mobileField);
this.append(choice);
this.addCommand(saveCommand);
this.addCommand(backCommand);
this.setCommandListener(this);
this.setItemStateListener(this);
}
Canvas类代表了MIDP的低级用户界面,它是一个抽象类。你需要继承Canvas并实现它的抽象方法paint(Graphics g)来构建你自己的Canvas实例。Paint()方法中的参数g非常重要。因为通过它提供的方法你才能在屏幕上绘画你的界面。如果有时间您应该多多研究一下Canvas类和Graphics类。在个人通信录中我们提供了一个WaitCanvas类并通过它构建了Dialog组件。从下面的代码中您能学会如何使用Canvas类。
package com.north.phonebook.ui;
import java.util.*;
import javax.microedition.lcdui.*;
public class WaitCanvas extends Canvas
{
PRivate int mCount, mMaximum;
private int mInterval;
private int mWidth, mHeight, mX, mY, mRadius;
private String mMessage;
private boolean run = false;
public WaitCanvas(String message, boolean run)
{
this.mMessage = message;
mCount = 0;
mMaximum = 36;
mInterval = 100;
mWidth = getWidth();
mHeight = getHeight();
// Calculate the radius.
int halfWidth = (mWidth - mRadius) / 2;
int halfHeight = (mHeight - mRadius) / 2;
mRadius = Math.min(halfWidth, halfHeight);
// Calculate the location.
mX = halfWidth - mRadius / 2;
mY = halfHeight - mRadius / 2;
// Create a Timer to update the display.
if (run)
{
TimerTask task = new TimerTask()
{
public void run()
{
mCount = (mCount + 1) % mMaximum;
repaint();
}
};
Timer timer = new Timer();
timer.schedule(task, 0, mInterval);
}
}
public void setMMessage(String message)
{
mMessage = message;
}
public void paint(Graphics g)
{
int theta = -(mCount * 360 / mMaximum);
g.setColor(255, 255, 255);
g.fillRect(0, 0, mWidth, mHeight);
g.setColor(128, 128, 255);
g.drawArc(mX, mY, mRadius, mRadius, 0, 360);
g.fillArc(mX, mY, mRadius, mRadius, theta + 90, 90);
g.fillArc(mX, mY, mRadius, mRadius, theta + 270, 90);
if (mMessage != null)
{
g.drawString(mMessage,mWidth/2,mHeight,Graphics.BOTTOM
Graphics.HCENTER);
}
}
}
下面我们看看MIDP中的事件处理机制,它同样分为高级事件处理和低级事件处理。高级事件处理由Command和Item事件组成。他们分别对应CommandListener和ItemStateListener接口。你可以在Displayable组件上添加Command并实现CommandListener接口。这个接口只定义了一个方法commandAction(Command cmd,Displayable),因此你要实现这个接口告诉应用程序当指定的command按下的时候它应该去执行什么操作,当然你不能忘记了注册Listener。例如:
public void commandAction(Command arg0, Displayable arg1)
{
if(arg0 == backCommand)
{
uicontroller.handleEvent
(UIController.EventID.EVENT_SEARCHUI_BACK_MAINNUI);
}
else if(arg0 == searchCommand)
{
String userName = inputField.getString();
if(userName.length()!= 0)
{
uicontroller.handleEvent(UIController.EventID.EVENT_SEARCH_RECORD_ANYWAY,new Object[]{userName});
}
}
}
ItemStateListener定义的方法是itemStateChanged(Item item),它的含义是当指定的item的内容发生变化的时候告诉应用程序去执行相应的操作,例如当TextField中用户输入了姓名,那么应用程序去RMS中去查询相关的记录并返回。例如
public void itemStateChanged(Item item)
{
if(item == inputField)
{
String userName = inputField.getString();
if(userName.length()!= 0)
{
uicontroller.handleEvent(UIController.EventID.EVENT_SEARCH_RECORD,new Object[]{userName});
}
}
}
MIDP中的低级事件处理是通过实现Canvas类的相关方法来实现的,例如当用户按下某个按键,应用程序应该去处理相应的操作。由于个人通信录中并未涉及相关内容因此不做讲解。
应用MVC设计模式实现界面导航
MIDP中的UI类使用起来比不难,然而界面导航问题却并不容易解决,事实上它是困扰很多J2ME程序员的问题。在MIDP中我们只能通过调用Display类中的setCurrent()方法来实现不同界面之间的切换,如果界面多起来比如有8-10个界面的时候就会显得非常的麻烦。你也许想构造一个树形的结构来记录每个界面的父亲界面例如:
public ChildUI(Displayable parent,Dispaly display)
{
this.parent = parent;
this.display = display;
}
但是当界面以及相互之间的联系增加的时候,界面的导航问题仍然是一个噩梦。MVC设计模式在Web application应用开发方面已经被证明是非常成功的,例如Apache的开源项目struts,在本文中我将讲述如何应用MVC设计模式解决MIDP应用程序的界面导航问题。
MVC的目的就是实现显示(View)与逻辑(Model)的分离,而在其中起到重要作用的就是控制器(Controller)。在控制器内通常我们要定义一些事件的代号以便和UI类通信,保证正确处理相应的事件,我们可以使用内部类来标记这些事件的代号。
public static class EventID
{
private EventID()
{
}
public static final byte EVENT_NEW_RECORD_SELECTED = 1;
public static final byte EVENT_SAVE_RECORD_SELECTED = 2;
public static final byte EVENT_NEWPHONE_BACK_MAINUI = 3;
public static final byte EVENT_LISTPHONE_BACK_MAINUI = 4;
public static final byte EVENT_SEARCHUI_BACK_MAINNUI = 5;
public static final byte EVENT_CLEAR_RECORD_YES = 6;
public static final byte EVENT_CLEAR_RECORD_NO = 7;
public static final byte EVENT_DELETE_RECORD = 8;
public static final byte EVENT_DELETE_RECORD_YES = 9;
public static final byte EVENT_DELETE_RECORD_NO = 10;
public static final byte EVENT_DISPLAY_INFOMATION = 11;
public static final byte EVENT_DETAIL_BACK_LIST = 12;
public static final byte EVENT_SEARCH_RECORD = 13;
public static final byte EVENT_SEARCH_RECORD_ANYWAY = 14;
public static final byte ADD_NEW_RECORD = 100;
public static final byte SEARCH_RECORD = 101;
public static final byte CLEAR_RECORD = 102;
public static final byte LIST_RECORD = 103;
public static final byte HELP = 104;
}
当UI类中有事件发生的时候它可以向UIController类传输事件的代码,UIController类根据代码来进行相应的事件处理。例如:
if (arg0 == backCommand)
{
uicontroller.handleEvent(UIController.EventID.EVENT_NEWPHONE_BACK_MAINUI)
}
UIController的handleEvent()方法则在接收到UI类的请求之后调用Model类的相关方法得到响应,然后再显示相关的界面。
public void handleEvent(int eventID)
{
switch (eventID)
{
case EventID.ADD_NEW_RECORD:
{
newPhoneUI.clear();
display.setCurrent(newPhoneUI);
break;
}
case EventID.CLEAR_RECORD:
{
dialog.setMessage(Title.delete_phonebook);
dialog.display(EventID.CLEAR_RECORD);
break;
}
case EventID.EVENT_CLEAR_RECORD_YES:
{
try
{
model.clearAllRecord();
display.setCurrent(indexFunctionUI);
} catch (ApplicationException e)
{
e.printStackTrace();
}
break;
}
……
……
}
有的时候我们不光要告诉控制类要做什么还要传输给他一些从界面类采集到的数据,这时候我们可以在UIController类中重载handleEvent()方法,添加一个Object[]类型的参数来接收数据,如下所示:
public void handleEvent(int eventID, Object[] obj)
{
switch (eventID)
{
case EventID.EVENT_SAVE_RECORD_SELECTED:
{
try
{
Account account = (Account) obj[0];
if (model.isRecordExist(account.getUserName()))
{
showAlert(Title.record_exist, indexFunctionUI,
AlertType.WARNING);
} else
{
model.addRecord(account);
showAlert(Title.record_added, indexFunctionUI,
AlertType.CONFIRMATION);
}
} catch (ApplicationException e)
{
e.printStackTrace();
}
break;
}
}
}
例如在添加新电话记录的时候,我们可以这样实现commandAction()方法向UIController传送消息。
if (arg0 == saveCommand)
{
……
……
Account newAccount = new Account(userName, mobilePhone, phone,
email);
uicontroller.handleEvent(
UIController.EventID.EVENT_SAVE_RECORD_SELECTED,
new Object[] { newAccount });
}
我们很难保证用户输入的数据有效,也很难保证用户的操作都合理,因此我们必须针对用户的不合理的操作给出相对的提示或者警告。在javax.microedition.lcdui包中Alert类能够很好的完成这个任务,因此我们自己提供一个方法如下所示:
public void showAlert(String message, Displayable next, AlertType type)
{
alert = new Alert(Title.alertTitle, message, null, type);
alert.setTimeout(1500);
setCurrent(alert, next);
}
当不合理的事件发生的时候我们应该调用它。
String userName = nameField.getString();
if (userName.length() == 0)
{
uicontroller.showAlert(Title.userNameNull, this,AlertType.WARNING);
return;
}
例如当用户并没有输入姓名就按下了保存的按钮的时候,应该提示用户“用户名不能为空”。
有些时候,某些操作可能会被堵塞,例如联网或者从RMS中读取大量的数据,这个时候我们应该使用多线程,多线程是java语言中内嵌的特性,使用起来也非常简单。在本例中当我们浏览的电话本中包含很多数据的时候,如果不使用多线程,主界面会持续几秒钟不动,这对用户来说很不友好,因为用户不知道现在应用程序在做什么,在这个时候使用多线程就显得非常必要。
关于RMS子系统的详细介绍请参考本站专题精通MIDP子系统RMS
本文从介绍J2ME平台,搭建开发环境到最后发布应用程序,详细的介绍了J2ME的开发过程,其中对MIDP的用户界面和Record Management System做了详细、深入的分析。这是本人在进行J2ME开发的一点经验和体会,希望和读者一起分享。由于水平有限,错误在所难免,欢迎大家批评指正
(出处:http://www.knowsky.com)