📄 feb02_ericg.txt
字号:
J 2 M E T E C H T I P S
TIPS, TECHNIQUES, AND SAMPLE CODE
WELCOME to the Java Developer Connection(sm) (JDC)
Java(tm) 2 Platform, Micro Edition (J2ME(tm))
Tech Tips, for February 26, 2002. This issue covers:
* Optimizing J2ME Application Size
* Object Serialization in CLDC-Based Profiles
The J2ME Tech Tips are written by Eric Giguere
(http://www.ericgiguere.com), an engineer at iAnywhere
Solutions, inc. Eric is the author of the book "Java 2 Micro
Edition: Professional Developer's Guide" and co-author of the
book "Mobile Information Device Profile for Java 2 Micro
Edition," both books in John Wiley & Sons' Professional
Developer's Guide series.
You can view this issue of the J2ME Tech Tips on the Web at
http://java.sun.com/jdc/J2METechTips/2002/tt0226.html
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OPTIMIZING J2ME APPLICATION SIZE
If there's anything that differentiates J2ME applications from
their J2SE(tm) counterparts, it's the limited environment in
which they run. The primary limitation in many J2ME systems is
the amount of memory available to store and run applications.
Many current MIDP devices, for example, limit application sizes
to 50K or less -- a far cry from the multi-megabyte applications
possible in server-based J2SE environments. It's very easy to run
up against these limits. So in this J2ME Tech Tip, you'll learn
some techniques for minimizing application size. You'll use these
techniques to reduce the size of the following MIDlet which does
nothing but display a text field and beep whenever its contents
change:
package com.j2medeveloper.techtips;
import javax.microedition.lcdui.*;
public class BeforeSizeOptimization extends
BasicMIDlet {
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
public BeforeSizeOptimization(){
}
protected void initMIDlet(){
getDisplay().setCurrent( new MainForm() );
}
public class MainForm extends Form {
public MainForm(){
super( "MainForm" );
addCommand( exitCommand );
append( textf );
setCommandListener( new CommandListener(){
public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
}
}
}
);
setItemStateListener(
new ItemStateListener() {
public void itemStateChanged(
Item item ){
if( item == textf ){
AlertType.INFO.playSound(
getDisplay() );
}
}
}
);
}
private TextField textf =
new TextField( "Type anything", null,
20, 0 );
}
}
Although a MIDlet serves as the example, the techniques described
here apply to size optimization for any J2ME profile.
Note that the MIDlet class shown above depends on the following
convenience class:
package com.j2medeveloper.techtips;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public abstract class BasicMIDlet extends MIDlet {
private Display display;
public BasicMIDlet(){
}
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}
public void exitMIDlet(){
notifyDestroyed();
}
public Display getDisplay(){ return display; }
protected abstract void initMIDlet();
protected void pauseApp(){
}
protected void startApp()
throws MIDletStateChangeException {
if( display == null ){
display = Display.getDisplay( this );
initMIDlet();
}
}
}
When packaged using the J2ME Wireless Toolkit, the sample MIDlet
is just over 4K bytes in size.
The first step in reducing size is to remove unnecessary classes
by pruning the application's functionality. Are all the features
of your application really necessary? Can your users get by
without all the "bells and whistles?" Build the most minimal
version of the application. Notice that the sample MIDlet is
pretty minimal already.
The second step is to look closely at any inner classes defined
by the application, particularly anonymous classes. Remember that
each class file has a certain amount of overhead associated with
it. Even the most trivial class has overhead:
public class foo {
// nothing here
}
Compile this class and you get a class file that is almost 200
bytes in size. A common use for an anonymous class, for example,
is to implement an event listener. The sample MIDlet defines two
listeners this way. An easy optimization to make, then, is to
make the main MIDlet class (which is not an optional or
unnecessary class) implement the CommandListener and
ItemStateListener interfaces, and move the listener code there.
Remember that multiple objects can use the same listeners. Use
the arguments passed to the commandAction and itemStateChanged
methods to distinguish between them if necessary.
Inner classes also bloat code other ways, because the compiler
must generate special variables and methods to allow an inner
class to access its enclosing class' private information. Read the
original inner class specification at
http://java.sun.com/products/jdk/1.1/docs/guide/innerclasses/
spec/innerclasses.doc.html for more details.
The third step is to maximize your use of pre-installed classes.
For example, in CLDC-based profiles don't build your own set of
collection classes. Use the built-in Hashtable and Vector classes
and work around their limitations. The same goes for form
creation in MIDP applications. The sample MIDlet defines a Form
subclass to create its main form, but it could just as easily
create it directly:
mainForm = new Form( "MainForm" );
mainForm.addCommand( okCommand );
mainForm.setCommandListener( listener );
There's no right or wrong answer, it's simply something to
consider.
The fourth step is to collapse your application's inheritance
hierarchies. You might have factored common code into one or more
abstract classes, a recommended technique for object-oriented
design that promotes code reuse between applications. It might
contradict what you've learned, but it might make more sense to
simplify the inheritance hierarchy. This is especially true if
your abstract class -- which might be from another project -- is
only subclassed once. The sample MIDlet extends the BasicMIDlet
class, for example, but the two are easily combined into a single
class.
The fifth step is to shorten the names of your packages, classes,
methods and data members. This might seem silly, but a class file
holds a lot of symbolic information. Shorten the names of things
and you'll reduce the size of the class file. The savings won't
be dramatic, but they can add up when spread over several
classes. Package names are particularly ripe for shortening.
Because MIDP applications are completely self-contained, you can
avoid package names completely -- there is no chance of conflict
with other classes on the device. The sample MIDlet could be
moved out of the com.j2medeveloper.techtips package.
Note that name shortening is not normally something you want to
do by hand. Instead, use a tool called an "obfuscator" to do it
for you. An obfuscator's primary purpose is to "hide" the code
for an application by making it nearly unreadable when
decompiled. A side effect of this process is to shrink the size
of the application. That's because the hiding is done primarily
by renaming methods and data members. There is an open source
obfuscator called RetroGuard that is available for free from
http://www.retrologic.com, and there are a number of commercial
packages also available. (When obfuscating code for CLDC-based
profiles, remember to do the obfuscation before the
preverification step, otherwise the obfuscation will invalidate
the preverification data stored in the class file.)
Finally, look closely at array initialization. (The sample MIDlet
doesn't do any array initialization, but this is an important
step for applications that do array initialization.) When
compiled, an array initialization statement such as this:
int arr[] = { 0, 1, 2, 3 };
actually produces code that behaves more like this:
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
arr[3] = 3;
To see this, use the javap tool that ships with the Java 2 SDK to
disassemble the byte code to a class (use the -c option). You
might be unpleasantly surprised at what you see, especially
if what you expected was a straight binary copy of constant data.
Two alternative approaches are to (1) encode the data into a
string and decode it into an array at runtime, or (2) store the
data as a binary file packaged with the application and make it
accessible at runtime using the class loader's
getResourceAsStream method.
These are just guidelines, and not every step mentioned here
makes sense for every J2ME application. However, most of them do
apply to the sample MIDlet. The optimized version of the MIDlet
looks like this:
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class ASO extends MIDlet
implements CommandListener,
ItemStateListener {
private Display display;
private Form mainForm;
private TextField mainFormTF =
new TextField( "Type anything", null,
20, 0 );
public static final Command exitCommand =
new Command( "Exit",
Command.EXIT, 1 );
public ASO(){
}
public void commandAction( Command c,
Displayable d ){
if( c == exitCommand ){
exitMIDlet();
}
}
protected void destroyApp( boolean unconditional )
throws MIDletStateChangeException {
exitMIDlet();
}
public void exitMIDlet(){
notifyDestroyed();
}
public Display getDisplay(){ return display; }
protected void initMIDlet(){
mainForm = new Form( "MainForm" );
mainForm.addCommand( exitCommand );
mainForm.setCommandListener( this );
mainForm.setItemStateListener( this );
mainForm.append( mainFormTF );
getDisplay().setCurrent( mainForm );
}
public void itemStateChanged( Item item ){
if( item == mainFormTF ){
AlertType.INFO.playSound( getDisplay() );
}
}
protected void pauseApp(){
}
protected void startApp()
throws MIDletStateChangeException {
if( display == null ){
display = Display.getDisplay( this );
initMIDlet();
}
}
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OBJECT SERIALIZATION IN CLDC-BASED PROFILES
The Connected Limited Device Configuration (CLDC) does not
support object serialization or reflection. This means that
unlike Mobile Information Device Profile (MIDP), there is no
built-in way to persist objects into a byte stream for
CLDC-based profiles. Applications that need to persist objects
must build their own persistence mechanisms. That's true
whether the need is to write the objects to a persistent storage
facility like MIDP's Record Management System (RMS) or
to send them across a network connection. It's hard to build
general persistence support that works with arbitrary objects.
However, writing task-specific persistence mechanisms is much
simpler and arguably more suited to the constraints of the CLDC
platform.
Persistence is easier to implement with the cooperation of the
class that is to be persisted. At the most basic level it's just
a matter of defining an interface like this:
import java.io.*;
/**
* A simple interface for building persistent objects on
* platforms where serialization is not available.
*/
public interface Persistent {
/**
* Called to persist an object.
*/
byte[] persist() throws IOException;
/**
* Called to resurrect a persistent object.
*/
void resurrect( byte[] data ) throws IOException;
}
Then you make your class implement the interface.
Consider a simple class describing an employee:
// Non-persistent version
public class Employee {
private int employeeID;
private String firstName;
private String lastName;
private int managerID;
public Employee( int employeeID, String firstName,
String lastName, int managerID ){
this.employeeID = employeeID;
this.firstName = firstName;
this.lastName = lastName;
this.managerID = managerID;
}
public int getID() { return employeeID; }
public String getFirstName() {
return firstName != null ? firstName : "";
}
public String getLastName() {
return lastName != null ? lastName : "";
}
public int getManagerID() { return managerID; }
}
The persistent version adds a null constructor and
implements the Persistent interface:
// Persistent version
import java.io.*;
public class Employee implements Persistent {
private int employeeID;
private String firstName;
private String lastName;
private int managerID;
public Employee(){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -