|
|
|
Java游戏开发规范_兼容真机
by 小扩
只读本文,即可从零写出能在 真功能机 / J2ME Loader 上运行的 J2ME(MIDP)程序,并完成编译打包。可下载附件丢给ai或者复制粘贴全文
API 是否存在:一律查 J2ME Docs,不要用桌面 JDK 文档。
一、先搞清目标
| 环境 | 作用 | | FreeJ2ME / KEmulator | 桌面调试方便,但常多实现了真机没有的 API,不能当验收标准 | | J2ME Loader(Android) | 日常验收;比真机宽松,但比桌面模拟器接近 | | 真功能机 | 最终标准;分辨率、按键、声音、文件路径因机而异 |
默认目标配置:CLDC-1.1 + MIDP-2.0。更老的机再降到 CLDC-1.0 / MIDP-1.0,并同步改 Manifest(见第七节)。
做不到「一份 JAR 通杀所有机」:本文解决的是「能编译、能启动、混淆不崩、API 不越界」;屏幕/按键/媒体仍要按机型适配。
备注:媒体操作的项目,比游戏类项目更难兼容
市面小游戏常写「全机型」,多半是:只测过主流 MIDP-2.0 机、主流程能玩就算成功,并非从 MIDP-1.0 黑白机到带 JSR-75 的机一份 JAR 功能全开。大厂当年也是多包、砍功能或只保核心玩法。
他们显得稳,是因为用了最低公因数玩法:
- 只用 Canvas + getGameAction + 少量 RMS,不碰文件 / HTTP / MMAPI
- JAR 往往几十 KB,老机装得下
- 布局简单,按 getWidth/getHeight 缩放或只适配一两档分辨率
- 不依赖签名与敏感权限
- 媒体失败就静音跳过,主循环不崩
媒体操作模拟 产品形态不同,兼容面天然更窄:
| 用到的能力 | 机型差异 | | JSR-75 文件 | 很多机没有;有也要权限 / 签名 | | MMAPI 音乐 | 格式、并发、路径因机而异 | | JAR 体积偏大(常数百 KB) | 不少老机上限更低 | | 时钟等逻辑用 double | 纯 CLDC-1.0 机直接不行 | | 浏览器 / 文件等完整功能 | 安全域一卡就挂 |
一句话:别人通杀的是「最低公因数玩法」;媒体操作要的是「多模块真机能力」。要接近前者的兼容面,只能:核心壳只靠 MIDP 标准 API,文件 / 音乐做成可选降级,再压体积——主界面在无 JSR 机上仍能进,高级功能有就开、没有就提示。
本文还覆盖四块真机落地内容:
| 主题 | 章节 | 解决什么 | | 机型适配模板 | 六 | 软键/布局/媒体可抄代码,不只写原则 | | 降级清单 | 七 | 老机 CLDC-1.0 / MIDP-1.0 要砍哪些 API | | 签名与权限 | 八 | 声明了权限却仍 SecurityException | | 安装与排障 | 九 | JAD+JAR 怎么装上;真机无日志怎么查 |
二、工程长什么样
- MyMidlet/
- src/
- HelloMidlet.java # 入口,extends MIDlet
- ...
- res/
- META-INF/MANIFEST.MF
- icon.png # 边长建议 ≤ 48
- java_lib/
- jsr139-cldcapi11.jar # CLDC 1.1 完整 API(不要用自写 stub)
- jsr118-midpapi20.jar # MIDP 2.0
- jsr075-pda.jar # 仅当用文件 API
- jsr135-mmapi.jar # 仅当用 Player
- compiler/
- ecj-4.20.jar
- proguard.jar
- default.pro
- build/ # 编译输出 .class
- release/
- unverified.jar # 未混淆,先测这个
- MyApp_v1.0.0.jar # 混淆后给 Loader/真机
- MyApp_v1.0.0.jad # 真机安装常用
复制代码
三条硬规则:
- 编译只用 完整 JSR JAR 当 bootclasspath,禁止自写 stub(stub 缺方法时桌面能过、真机才炸)。
- 不要把 javax.microedition.* 的 class 打进自己的 JAR(机子自带实现)。
- 资源路径大小写与 JAR 内一致;代码里用 "/icon.png" 这种以 / 开头的路径。
JSR JAR 来源:Sun/Oracle Java ME SDK、WTK 2.5.2 的 lib/,或任何带完整 CLDC/MIDP 签名的工具链包。文件名不重要,内容必须是真 API。
三、怎么写代码(核心)
3.1 最小可运行 MIDlet
- import javax.microedition.lcdui.Display;
- import javax.microedition.lcdui.Form;
- import javax.microedition.midlet.MIDlet;
- public class HelloMidlet extends MIDlet {
- protected void startApp() {
- Form form = new Form("Hello");
- form.append("OK");
- Display.getDisplay(this).setCurrent(form);
- }
- protected void pauseApp() {
- // 停定时器、停 Player、可释放大图
- }
- protected void destroyApp(boolean unconditional) {
- // 关闭文件 / RMS / Player
- }
- }
复制代码
Manifest 里 MIDlet-1 的第三段必须是这个类的全名(无包则 HelloMidlet,有包则 com.foo.HelloMidlet)。
3.2 语言与标准库:只能用缩水版
按 Java 1.3 写。下面这些在 CLDC 里 没有或不完整:
| 禁止 | 正确写法 | | ArrayList / HashMap / HashSet | Vector / Hashtable / 数组 | | StringBuilder | StringBuffer | | 泛型、enum、增强 for、... 变长参数、注解 | 普通 Java 1.3 | | Math.round(x) | 见下方 | | String.format / printf | StringBuffer 拼接 | | java.util.concurrent.* | Thread + synchronized + wait/notify | | 依赖 System.out 当日志 | 真机无控制台;用 Loader 日志或自己写文件/RMS | | 反射乱挖私有 API | 真机常禁或不全;不要依赖 |
四舍五入(CLDC 有 Math.floor,无 round):
- private static int round(double v) {
- return (int) Math.floor(v + 0.5);
- }
复制代码
负数边界若要与 J2SE Math.round 完全一致,自行加单测;一般 UI 坐标用上面即可。
每用一个 java.lang / java.util / java.io 方法,都到 J2ME Docs 确认存在。
3.3 LCDUI 必记
TextBox 约束常量在 TextField 上(写 TextBox.ANY 会在真 API 下编不过,或在 stub 下误通过):
- import javax.microedition.lcdui.TextBox;
- import javax.microedition.lcdui.TextField;
- // 正确
- TextBox box = new TextBox("标题", "", 64, TextField.ANY);
- // TextField.NUMERIC / PASSWORD / URL / PHONENUMBER ...
复制代码
Canvas.paint 必须是 protected:
- import javax.microedition.lcdui.Canvas;
- import javax.microedition.lcdui.Graphics;
- public class GameCanvas extends Canvas {
- protected void paint(Graphics g) { // 必须 protected
- g.setColor(0xFFFFFF);
- g.fillRect(0, 0, getWidth(), getHeight());
- }
- }
复制代码
布局用 getWidth() / getHeight(),不要写死 240×320。全屏:setFullScreenMode(true)(MIDP 2)。
按键:优先 getGameAction(keyCode)(UP/DOWN/LEFT/RIGHT/FIRE)。左右软键键码 各厂商不同,不要只认诺基亚的 -6/-7,做成可配置映射(见第六节模板)。
多数功能机 无触屏:主流程必须能全靠按键完成;pointerPressed 只能当附加。
字体只有 Font.SIZE_SMALL / MEDIUM / LARGE 与 FACE_* / STYLE_*,不要假设像素字号。
3.4 多界面时:不要让 Canvas 实现带 paint 的业务接口
接口里的方法在字节码中是 public。若业务接口也叫 paint(Graphics),而 Canvas 子类去 implements 它,就会和 MIDP 要求的 protected paint 冲突——未混淆可能碰巧能跑,ProGuard 合并接口/类之后 Loader/真机直接 IllegalAccessError。
正确拆分:
- import javax.microedition.lcdui.Graphics;
- /** 业务界面:paint 必须是 public */
- public interface AppScreen {
- void paint(Graphics g);
- void keyPressed(int keyCode);
- }
- /** 仅按键回调,不要声明 paint */
- public interface KeyHandler {
- void onKey(int keyCode);
- }
复制代码
- import javax.microedition.lcdui.Canvas;
- import javax.microedition.lcdui.Graphics;
- public class AppCanvas extends Canvas implements KeyHandler {
- private AppScreen screen;
- public void setScreen(AppScreen s) { screen = s; }
- protected void paint(Graphics g) { // Canvas 要求:protected
- if (screen != null) screen.paint(g);
- }
- public void onKey(int keyCode) { }
- protected void keyPressed(int keyCode) {
- if (screen != null) screen.keyPressed(keyCode);
- repaint();
- }
- }
复制代码
- import javax.microedition.lcdui.Graphics;
- public class MenuScreen implements AppScreen {
- public void paint(Graphics g) { // 接口要求:public
- g.drawString("Menu", 0, 0, Graphics.TOP | Graphics.LEFT);
- }
- public void keyPressed(int keyCode) { }
- }
复制代码
规则一句话:Canvas 子类只负责转发;带 paint 的接口只由 Screen 类实现。
3.5 生命周期与线程
- startApp / pauseApp / destroyApp 要能反复进出。
- pauseApp:停 Timer、停 Player、可丢掉大图。
- destroyApp:关 RMS、文件、Player(deallocate + close)。
- 不要在 paint 里读文件、解码大图、发网络请求。
- 自建线程里不要直接改 LCDUI / 碰 Graphics。后台算完后,用 Display.callSerially 回到事件线程再改界面:
- import javax.microedition.lcdui.Display;
- // 在工作线程末尾:
- display.callSerially(new Runnable() {
- public void run() {
- // 这里才改 Form / 切 Screen / 调 repaint()
- canvas.repaint();
- }
- });
复制代码
Timer / TimerTask 回调同样当作后台线程:需要改 UI 时走 callSerially。
3.6 RMS / 文件 / 网络 / 声音
RMS(小配置):
- import javax.microedition.rms.RecordStore;
- RecordStore rs = RecordStore.openRecordStore("cfg", true);
- // ... read/write ...
- rs.closeRecordStore();
复制代码
空间很小(常只有几十~几百 KB),不要当大文件盘。注意 RecordStoreFullException。
文件(JSR-75):Manifest 声明权限;路径用 FileSystemRegistry.listRoots(),不要写死 C:。无此 JSR 的机不要编译进文件代码,或做成可选。
HTTP:需权限与用户授权;HTTPS/证书行为分裂,不要假设桌面那套。超时与断网要 catch,不要卡死事件线程。
MMAPI 声音:格式看机型(Tone / MIDI / WAV / AMR / MP3)。用完:
- player.stop();
- player.deallocate();
- player.close();
- player = null;
复制代码
同时尽量只保持一个 Player。大图:缩小后再 Image.createImage,用完丢掉引用,防 OutOfMemoryError。
3.7 中文、编码与资源
- JAR 内路径大小写敏感(Windows 开发时最容易踩坑)。
- 不少功能机中文字体缺字 → 方框;重要 UI 可用图片字,或接受系统字体限制。
- 文本文件编码:优先 UTF-8 并自己解析;部分机 new String(bytes, "UTF-8") 会抛异常,需 catch 后回退 ISO-8859-1 或机型已知编码。
- 图片:PNG 最稳;JPEG/透明度因机而异。图标建议不透明小 PNG,边长 ≤ 48。
- 资源用 Image.createImage("/foo.png") 或 getClass().getResourceAsStream("/foo.png"),路径以 / 开头且与 JAR 内一致。
3.8 禁止依赖厂商私有 API
桌面模拟器常带 Nokia UI、Samsung、Siemens 等扩展包,真机未必有。默认只允许:
- CLDC + MIDP(javax.microedition.* 标准子集)
- 明确目标机支持的可选 JSR(如 JSR-75、JSR-135)
不要 import com.nokia.* / com.samsung.* 等,除非你在为单一厂商机型出专用包,并在验收机上实测。
四、Manifest 与 JAD
4.1 res/META-INF/MANIFEST.MF
- Manifest-Version: 1.0
- MIDlet-Name: 应用名
- MIDlet-Version: 1.0.0
- MIDlet-Vendor: 厂商
- MIDlet-Icon: /icon.png
- MIDlet-1: 应用名, /icon.png, HelloMidlet
- MicroEdition-Profile: MIDP-2.0
- MicroEdition-Configuration: CLDC-1.1
复制代码
用了文件读写时追加(行宽超过 72 字符必须折行,续行以一个空格开头):
- MIDlet-Permissions: javax.microedition.io.Connector.file.read,javax.micr
- oedition.io.Connector.file.write
复制代码
其它权限按需(乱写可能导致装不上):
- javax.microedition.io.Connector.http
- javax.microedition.io.Connector.https
- javax.microedition.io.Connector.socket
检查:MIDlet-1 第三段 = 真实入口类;图标路径在 JAR 里存在。
权限与签名的关系见第八节:声明了权限 ≠ 未签名包一定能用。
4.2 MyApp_v1.0.0.jad(真机常用)
与 Manifest 关键字段保持一致,并增加:
- MIDlet-Jar-URL: MyApp_v1.0.0.jar
- MIDlet-Jar-Size: 123456
复制代码
MIDlet-Jar-Size = 最终 JAR 的字节数(混淆后要重算)。JAD 与 JAR 放同一目录。
五、怎么编译打包
流水线:
- 源码 → ECJ/javac(1.3, bootclasspath=真JSR) → jar cfm → unverified.jar
- → ProGuard -microedition → 最终.jar → 写 JAD
- → 先测 unverified,再测最终包 → 真机再测
复制代码
5.1 编译(ECJ 示例)
假设在项目根目录,Linux/macOS 用 :,Windows 的 java.exe 用 ;:
- # bootclasspath / classpath 都指向这些 JSR,不要用桌面 rt.jar
- java -cp java_lib/compiler/ecj-4.20.jar ^
- org.eclipse.jdt.internal.compiler.batch.Main ^
- -1.3 -encoding UTF-8 -proc:none -g:none ^
- -bootclasspath "java_lib/jsr139-cldcapi11.jar;java_lib/jsr118-midpapi20.jar" ^
- -classpath "java_lib/jsr139-cldcapi11.jar;java_lib/jsr118-midpapi20.jar" ^
- -d build src
复制代码
也可用 javac -source 1.3 -target 1.1,同样必须设 bootclasspath 为 JSR。
5.2 打包未混淆 JAR
- jar cfm release/unverified.jar res/META-INF/MANIFEST.MF -C res . -C build .
复制代码
自检:jar tf release/unverified.jar 应看到 META-INF/MANIFEST.MF、入口 .class、icon.png;不应出现整份 JDK 的 java/lang/String.class 或把 JSR 实现打进去。
5.3 ProGuard(完整可用配置)
保存为 java_lib/compiler/default.pro:
- -verbose
- -dontnote
- -microedition
- -overloadaggressively
- -repackageclasses ''
- # 禁止打开下面两行(Canvas + 自定义 paint 接口时会 IllegalAccessError):
- # -mergeinterfacesaggressively
- # -allowaccessmodification
- -keep class * extends javax.microedition.lcdui.Canvas
- -keep public class * extends javax.microedition.midlet.MIDlet
- -keepclasseswithmembernames class * {
- native <methods>;
- }
复制代码
运行(-libraryjars 指向 JSR,不要 injars):
- java -cp java_lib/compiler/proguard.jar proguard.ProGuard ^
- -injars release/unverified.jar ^
- -outjars release/MyApp_v1.0.0.jar ^
- -libraryjars java_lib/jsr139-cldcapi11.jar ^
- -libraryjars java_lib/jsr118-midpapi20.jar ^
- -include java_lib/compiler/default.pro
复制代码
混淆后建议 javap 抽查:
- 入口类仍 extends MIDlet
- Canvas 子类的 paint 仍是 protected
- 实现 AppScreen 的类 paint 为 public
- Canvas 没有 implements 带 paint 的业务接口
5.4 Preverify(真机)
部分老机要求 class 经过 WTK / Java ME SDK 的 preverify。ProGuard 的 -microedition 会做 ME 相关处理;若真机仍报 verify / 类校验错误,用官方工具处理后再打包:
- # 示例:WTK / Java ME SDK 自带 preverify(路径按本机安装改)
- preverify -classpath "java_lib/jsr139-cldcapi11.jar;java_lib/jsr118-midpapi20.jar" ^
- -d build_preverified build
- jar cfm release/MyApp_v1.0.0.jar res/META-INF/MANIFEST.MF ^
- -C res . -C build_preverified .
复制代码
Loader 能跑 ≠ 真机能装。
5.5 一份可直接改的 make.sh(含 Windows 与 JAD)
- #!/usr/bin/env bash
- set -e
- ROOT="$(cd "$(dirname "$0")" && pwd)"
- cd "$ROOT"
- rm -rf build release
- mkdir -p build release
- APP_NAME="MyApp_v1.0.0"
- # Windows Git Bash / MSYS 下原生 java.exe 需要 ';'
- case "$(uname -s 2>/dev/null)" in
- MINGW*|MSYS*|CYGWIN*) CP_SEP=';' ;;
- *) CP_SEP=':' ;;
- esac
- LIBS="$(find java_lib -maxdepth 1 -name '*.jar' | sort | tr '\n' "$CP_SEP" | sed "s/${CP_SEP}\$//")"
- java -cp java_lib/compiler/ecj-4.20.jar \
- org.eclipse.jdt.internal.compiler.batch.Main \
- -1.3 -encoding UTF-8 -proc:none -g:none \
- -bootclasspath "$LIBS" -classpath "$LIBS" \
- -d build src
- jar cfm release/unverified.jar res/META-INF/MANIFEST.MF -C res . -C build .
- PG_ARGS=""
- for j in java_lib/*.jar; do
- PG_ARGS="$PG_ARGS -libraryjars $j"
- done
- java -cp java_lib/compiler/proguard.jar proguard.ProGuard \
- -injars release/unverified.jar \
- -outjars "release/${APP_NAME}.jar" \
- $PG_ARGS \
- -include java_lib/compiler/default.pro
- # 生成 JAD(字段与 Manifest 保持一致,按项目改名称)
- SIZE=$(wc -c < "release/${APP_NAME}.jar" | tr -d ' ')
- cat > "release/${APP_NAME}.jad" <<EOF
- MIDlet-Name: 应用名
- MIDlet-Version: 1.0.0
- MIDlet-Vendor: 厂商
- MIDlet-Jar-URL: ${APP_NAME}.jar
- MIDlet-Jar-Size: ${SIZE}
- MIDlet-Icon: /icon.png
- MIDlet-1: 应用名, /icon.png, HelloMidlet
- MicroEdition-Profile: MIDP-2.0
- MicroEdition-Configuration: CLDC-1.1
- EOF
- echo "OK: release/unverified.jar / release/${APP_NAME}.jar / release/${APP_NAME}.jad"
复制代码
更完整的 Windows 路径转换(cygpath、参数文件)可参考本仓库 project/make.sh。
体积:很多老机对 JAR 有硬上限(常见约 64KB~数百 KB,因机而异)。资源过大时先砍图、砍音、加强混淆,再谈功能。
六、机型差异与可抄模板
原则不够用时,直接抄下面骨架,再按实测改常量。
6.1 原则表
| 维度 | 写法要求 | | 屏幕 | 只用 getWidth/getHeight 布局;按宽度档位换布局(如 <176 / <240 / ≥240) | | 软键 | 键码可配置,不写死单一厂商 | | 内存 | 少同时持有大图;及时释放 Player/Image | | 文件 | listRoots();无 JSR-75 则降级 | | 声音 | 格式失败要 catch,不要假定 MP3 | | 触屏 | 按键可走完主流程 | | 权限 | SecurityException 时提示或关闭功能 | | 编码/字体 | UTF-8 失败回退;关键文案可图片字 |
6.2 软键映射模板
- public final class SoftKeys {
- // 可按机型改这两个值;诺基亚常见 -6/-7,其它厂可能不同
- public static int LEFT = -6;
- public static int RIGHT = -7;
- public static boolean isLeft(int keyCode) {
- return keyCode == LEFT;
- }
- public static boolean isRight(int keyCode) {
- return keyCode == RIGHT;
- }
- /** 调试屏:把实际按下的 keyCode 显示出来,写进配置 */
- public static void learn(int keyCode) {
- // 显示 "key=" + keyCode,让用户指定左/右软键
- }
- }
复制代码
全屏 Canvas 时左右软键常靠 keyPressed;非全屏也可用 Command + CommandListener(更标准,但样式因机而异)。
6.3 屏幕档位布局模板
- int w = canvas.getWidth();
- int h = canvas.getHeight();
- int pad = (w < 176) ? 4 : (w < 240) ? 6 : 8;
- int line = (w < 176) ? 14 : 16;
- // 列表行高、图标尺寸都跟 w/h 走,禁止写死 240x320
复制代码
6.4 媒体/文件失败降级
- try {
- player = Manager.createPlayer(is, "audio/midi");
- player.start();
- } catch (Exception e) {
- // 静音继续,不要让整个 MIDlet 崩
- player = null;
- }
- try {
- roots = FileSystemRegistry.listRoots();
- } catch (Throwable t) {
- roots = null; // UI 提示「本机无文件访问」
- }
复制代码
七、降到 CLDC-1.0 / MIDP-1.0(降级清单)
目标机若只有更老 Profile,除改 Manifest 外,代码也要跟着砍。
7.1 Manifest
- MicroEdition-Profile: MIDP-1.0
- MicroEdition-Configuration: CLDC-1.0
复制代码
编译用对应的 CLDC 1.0 / MIDP 1.0 JSR JAR,不要继续链 CLDC 1.1 API 却声称 1.0。
7.2 CLDC-1.0 常见砍掉的能力
| 能力 | 说明 | | float / double | 没有浮点类型与 Math 浮点方法;改定点数(整数放大 100/1000 倍) | | 部分库方法 | 仍以 J2ME Docs / 1.0 JAR 为准,不要假设 1.1 有的都在 |
7.3 MIDP-1.0 常见砍掉的能力
| 能力 | 说明 | | Canvas.setFullScreenMode | 没有;接受系统标题栏/软键栏占位 | | GameCanvas / Layer / Sprite 等 game 包 | MIDP 2 才有;自己用 Canvas 画 | | 部分 LCDUI / 媒体细节 | 以 1.0 文档为准 |
策略:能两套包就出 xxx_midp2.jar 与 xxx_midp1.jar;或主路径只用 1.0 子集(老机反射也弱,优先分包)。
八、权限、签名与安全域
真机通常把 MIDlet 放在不同安全域(未信任 / 信任 / 厂商等)。
| 情况 | 常见结果 | | 未声明 MIDlet-Permissions | 文件/网络等敏感 API 直接不可用或装不上 | | 声明了权限,但包未签名 | 能装、能启动,一用文件/网络就 SecurityException 或反复弹授权(因机而异) | | 用厂商/运营商认可的证书签名 | 才较可能静默获得文件、HTTP 等权限 |
实务建议:
- Manifest 只声明真正用到的权限。
- 所有 Connector.open / FileConnection / HTTP 调用包 try/catch (SecurityException e),失败时给用户可读提示并关闭该功能。
- 若目标真机要求签名:用 Java ME / WTK 的签名工具对 JAR 签名,并按厂商文档导入证书;JAD 与 JAR 的签名相关字段保持一致。
- Loader 上「一切畅通」不能证明未签名包在真机有文件权限。
开发阶段可用「无文件/无网络」功能集先过启动验收,再单独做权限机型矩阵。
九、真机安装与无日志排障
9.1 常见安装途径
| 方式 | 说明 | | 蓝牙 / USB 传 JAD+JAR | 多数机要求二者同目录、Size 正确 | | 存储卡拷贝 | 部分机从文件管理器「打开」JAR/JAD | | OTA(HTTP 下 JAD) | MIDlet-Jar-URL 必须可下载;服务器 MIME 常要配对 |
装不上时优先查:JAD Size、入口类名、图标是否存在、Profile 是否高于手机、JAR 是否超体积上限。
9.2 真机几乎没有控制台时
- 先在 J2ME Loader 看 JL-Mod/log.txt(路径视加载器而定)。
- 真机:自写「调试屏」显示最后错误字符串;或写入 RMS /(有权限时)小日志文件。
- 二分法:unverified → 混淆包;无文件版 → 有文件版;缩小资源后再装。
- 安装失败但无文案:换 JAD、核对 Size、试只装更小的 hello MIDlet 验证机子 Java 环境。
十、验收顺序
[] 真 JSR 编译通过(无 stub、无厂商私有 API)。
[] unverified.jar 在 J2ME Loader 能进主界面。
[] 混淆后的 JAR 也能进(必测;paint/接口问题只在这一步暴露)。
[] pauseApp / 退出不崩;无触屏仅按键可玩;后台线程改 UI 走 callSerially。
[] 目标真机:JAD+JAR 能装、能启动;核对 MIDlet-Jar-Size。
[] 若用文件/网络:在真机验证权限(含签名策略),SecurityException 有降级提示。- 老机:按第七节降 Profile 的包单独测。
十一、排错
| 现象 | 处理 | | 编译缺 Math.round 等 | 换 CLDC 写法;查 J2ME Docs | | ANY 找不到 | TextField.ANY,不要 TextBox.ANY | | 安装后找不到 MIDlet | 查 MIDlet-1 类名、图标、JAD Size | | 未混淆能跑、混淆崩 IllegalAccessError + paint | 关掉 mergeinterfaces / allowaccessmodification;keep Canvas;按 3.4 拆接口 | | NoSuchMethodError / NoClassDefFoundError | 用了机子没有的 JSR、更高 Profile,或厂商私有 API | | OutOfMemoryError | 缩图、释放 Player/Image、减小 JAR | | SecurityException | 权限未声明 / 未签名 / 用户拒绝;catch 并降级 | | 类校验 / verify 失败 | 走 5.4 preverify;确认 -microedition | | 仅 FreeJ2ME 能跑 | 模拟器多实现了 API;用真 JSR 重编,改在 Loader/真机测 | | 中文方框 / 乱码 | 字体缺字用图;编码回退;勿依赖桌面默认 UTF-8 | | 软键无反应 | 用调试屏记录 keyCode,改 SoftKeys 映射 |
Loader 日志常见路径:设备上的 JL-Mod/log.txt(视具体加载器而定)。
十二、从零检查清单(可放到项目里打勾)
写代码
[] 语法 ≤ Java 1.3;集合用 Vector/Hashtable
[] 无 Math.round / StringBuilder / ArrayList / HashMap
[] 无厂商私有 API(除非单机专用包)
[] TextBox 约束来自 TextField
[] Canvas.paint 为 protected;业务 Screen 的 paint 为 public;Canvas 不实现业务 paint 接口
[] 布局与按键不写死单一机型;主流程可纯按键;软键可配置
[] 后台改 UI 使用 Display.callSerially
[] pause/destroy 释放 Timer、Player、大图、文件- 文件/网络/播放失败有 catch 与降级
工程与包
[] bootclasspath = 真 JSR,无 stub
[] Manifest / 入口类 / 图标正确;超长行已折行
[] JAR 未打入 javax.microedition 实现
[] 先测 unverified,再测 ProGuard 包
[] ProGuard 含 -microedition、keep MIDlet、keep Canvas;未开 mergeinterfacesaggressively / allowaccessmodification
[] 真机需要时有 JAD,Size 正确;体积不超过目标机上限
[] 需要时做过 preverify
[] 敏感 API 的签名/权限策略已按目标机验证
老机 / 真机
[] 若目标为 CLDC-1.0:无浮点
[] 若目标为 MIDP-1.0:无 setFullScreenMode / Game API 等- 真机安装途径已试通;无日志时有调试屏或 RMS 日志
API 依据
(文档结束) |
|