無縫移植J2ME程序到OPhone平臺解決方案
為什么需要移植J2ME程序
1990年12月,Sun公司內部由James Gosling、Patrick Naughton及Mike Sheridan成立了一個叫做Green Team的小組。Green Team小組的主要目標,是要發(fā)展一種新架構,而這種架構必須能夠 在消費性電子產品作業(yè)平臺上運行,現在我們普遍認識的PDA、手機或是信息家電(IA)等,都是屬于這種架構的目標平臺。接著,Green Team在1992年9月3號,發(fā)表了一款由Java技術之父James Gosling所領導研發(fā)的名叫Star Seven(*7)的機器,研發(fā)出一部交互式的掌上型家用娛樂裝置,可通過使用動畫觸碰式屏幕的使用者接口來控制其它電子設備。
1999年,Sun公司把Java切割成J2SE、J2ME、J2EE,所以有了J2ME這個名詞的出現。經過將近20年的發(fā)展,J2ME已經滲入到嵌入式設備的各個領域,J2ME在手機設備上的已有應用程序更是數以千萬計。
OPhone平臺的Java虛擬機稱為“Dalvik”,它是Google公司自己設計用于Android平臺的Java虛擬機,通過查看OPhone SDK中的例子可以發(fā)現存在J2ME和OPhone很多類庫并存的問題。#t#
那么如何移植已經存在的數以千萬計的J2ME應用程序到OPhone平臺就成為第三方應用程序開發(fā)者亟待解決的問題,因為如果全部重新在OPhone平臺開發(fā)的話必然會花費很大的時間成本和人力成本,而且使得整個產品變得難以升級和維護,甚至錯失寶貴的市場機遇。
如果存在這樣一個解決方案,即現有的J2ME應用程序可以不用修改或者只需要做很少的修改就能運行在OPhone平臺,這樣開發(fā)人員只需要維護一份代碼,產品升級和維護也變得簡單,企業(yè)也節(jié)省了開發(fā)成本和時間成本,那么對OPhone平臺的發(fā)展無疑是最有益的。
筆者這篇文章就致力于這樣一個解決方案,使得現有的J2ME程序只需要做很少的修改就能無縫運行在OPhone平臺上,筆者把這個解決方案所需要的源代碼單獨放入了Wrap包,下文中就統一稱為“J2ME適配包”。
J2ME適配包之架構
現有的J2ME應用程序的界面顯示以及按鍵響應基本都是通過GameCanvas類接口進行處理的,但OPhone平臺下并沒有這個類。OPhone平臺下通過Canvas類和Paint類聯合控制繪圖句柄,大量的繪制工作如繪制直線、繪制矩形、繪制字符串、繪制圖片等操作都在Canvas類里完成,而Paint類則負責視圖的布局如文本對齊方式、筆刷填充方式、字體大小和字體樣式等工作都在Paint類里完成,所以開發(fā)人員需要自己實現這樣一個GameCanvas類,并提供相應的繪圖句柄接口可以讓OPhone平臺通過簡單的接口調用無縫運行原有的J2ME應用程序,整個J2ME適配包的架構如下:
圖1 J2ME適配包架構圖
適配包開發(fā)的核心思想是創(chuàng)建J2ME程序無縫運行的環(huán)境。在圖1中,GameView類通過實例化Image類創(chuàng)建了J2ME程序運行的窗口,并通過Image類的getGraphics()方法獲取圖形上下文句柄Graphics對象canvasGraphics,最后通過方法setScreen(GameCanvas screen)傳遞給原有的J2ME程序主屏幕類,從而使得原有的J2ME程序可以通過圖形上下文句柄canvasGraphics對象進行視圖繪制和布局控制,其中一些關鍵的技術筆者會在下文中分述。#p#
J2ME適配包之界面
OPhone平臺除了在一些接口上采用了和J2ME不一樣的名稱之外,基本的接口內容還是一樣的,筆者這里利用OPhone接口重寫了J2ME中的Graphics和Image等類,原有的J2ME程序就可以無縫的運行在OPhone平臺上。
既然要重寫J2ME下的這些接口,那么就要先了解OPhone平臺和J2ME平臺到底有哪些不同。
1:Font類
OPhone平臺里不再提供J2ME下面的Font類,而是以Typeface代替,這為讀者提供了更多的選擇空間,因為Typeface是可以自定義的,但是為了無縫移植J2ME程序到OPhone平臺上,需要封裝了一個類似于J2ME平臺下的Font類。
定義字體格式,J2ME平臺下的字體有三種風格STYLE_PLAIN、STYLE_BOLD以及STYLE_ITALIC,分別代表了普通字體、粗體和斜體。OPhone平臺下通過Typeface類的靜態(tài)方法defaultfromstyle()從默認的字體庫中獲取不同風格的字體,如下:
- private static final Font DEFAULT_FONT = new Font(null,22);
- public static Font getFont(int face,int style,int size)
- {
- switch(style)
- {
- case STYLE_PLAIN:
- return new Font(Typeface.defaultFromStyle(Typeface.NORMAL),
- size);
- case STYLE_BOLD:
- return new Font(Typeface.defaultFromStyle(Typeface.BOLD),
- size);
- case STYLE_ITALIC:
- return new Font(Typeface.defaultFromStyle(Typeface.ITALIC),
- size);
- }
- return DEFAULT_FONT;
- }
筆者這里定義的默認字體DEFAULT_FONT大小為22像素,開發(fā)者可以根據屏幕大小和項目需要自定義三種風格字體的大小。除此之外,還需要定義Font類常用的接口,比如獲取字符寬度以及獲取字體高度等,代碼如下:
- public int charWidth(char arg)
- {
- return size;
- }
- public int stringWidth(String arg)
- {
- return size*(arg.length());
- }
2:Graphics類
OPhone平臺下不再提供Graphics類進行“視圖上下文”的控制,而是以Canvas類和Paint類聯合進行控制,這邊筆者在上文已經交代過。
Graphics類在J2ME平臺下是繪圖操作和布局格式控制的句柄,OPhone平臺下把繪圖操作和布局格式控制分別在Canvas類和Paint類里進行了實現。那么如果想無縫移植J2ME應用程序到OPhone平臺,就需要利用OPhone平臺的Cavans類和Paint類實現類似J2ME平臺的Graphics類。
Canvas類需要一個Image參數作為繪制操作的窗口,那么筆者這里就傳遞一個Image參數到Graphics類以初始化Canvas類,如下:
- public Graphics(Bitmap bitmap) {
- this.bitmap = bitmap;
- this.canvas = new Canvas(bitmap);
- this.canvas.clipRect(0, 0, bitmap.getWidth(), bitmap.getHeight());
- this.canvas.save(Canvas.CLIP_SAVE_FLAG);
- this.paint = new Paint();
- this.clip = canvas.getClipBounds();
- }
接下來就封裝了一下J2ME平臺下Graphics類的繪制操作,比如畫線、畫圖、畫字符串等操作,代碼如下:
- public void fillTriangle(int x1,int y1,int x2,int y2,int x3,int y3)
- {
- paint.setStyle(Style.FILL);
- canvas.drawLine(x1, y1, x2, y2, paint);
- canvas.drawLine(x2, y2, x3, y3, paint);
- canvas.drawLine(x3, y3, x1, y1, paint);
- }
- public void setFont(Font font)
- {
- paint.setTypeface(font.getTypeface());
- paint.setTextSize(font.getSize());
- FontMetrics fontMetrics = paint.getFontMetrics();
- float height = fontMetrics.bottom-fontMetrics.top;
- font.setHeight((int)height);
- }
- public void fillArc(int x,int y,int width,int height,
- int startAngle,int arcAngle)
- {
- paint.setStyle(Style.FILL);
- canvas.drawArc(new RectF(x,y,width,height),
- startAngle, arcAngle, true, paint);
- }
- public void drawArc(int x,int y,int width,int height,
- int startAngle,int arcAngle)
- {
- paint.setStyle(Style.STROKE);
- canvas.drawArc(new RectF(x,y,width,height),
- startAngle, arcAngle, true, paint);
- }
除此之外,還需要在Graphics類里封裝J2ME下布局控制的代碼,OPhone平臺下利用Paint類進行布局控制,所以筆者在Graphics的構造函數里初始化了一個Paint對象,J2ME平臺下常見的布局控制是文本對齊方式以及畫刷填充格式等,封裝如下:
- public void setAlign(int align)
- {
- if(LEFT == align
- ||(Graphics.LEFT | Graphics.TOP) == align
- ||(Graphics.LEFT | Graphics.BOTTOM) == align)
- {
- paint.setTextAlign(Align.LEFT);
- }else if(HCENTER == align
- ||(Graphics.HCENTER|Graphics.TOP) == align)
- {
- paint.setTextAlign(Align.CENTER);
- }else if(RIGHT == align
- ||(Graphics.RIGHT | Graphics.TOP) == align)
- {
- paint.setTextAlign(Align.RIGHT);
- }
- }
另外,J2ME平臺里設置Graphics繪圖句柄的顏色有兩種格式:setColor(int r,int g,int b)和setColor(0xRGB),而OPhone平臺通過setColor(argb)設置畫筆顏色或畫刷顏色,代碼封裝如下:
- public void setColor(int rgb) {
- paint.setColor(rgb);
- }
- public void setColor(int r,int g,int b) {
- int argb = (0xff000000)+(r<<16)+(g<<8)+b;
- paint.setColor(argb);
- }
通過Graphics類的setFont(Font font)方法可以設置繪圖句柄的字體,筆者這里通過上面封裝的Font類設置繪圖句柄的字體,代碼如下:
- public void setFont(Font font)
- {
- paint.setTypeface(font.getTypeface());
- paint.setTextSize(font.getSize());
- FontMetrics fontMetrics = paint.getFontMetrics();
- float height = fontMetrics.bottom-fontMetrics.top;
- font.setHeight((int)height);
- }
上面的代碼中,筆者通過FontMetrics類獲取字體的高度信息,并調用Font類的setHeight()接口傳遞高度信息到Font類里。
最后,需要提供一些接口給原有的J2ME應用程序,以方便的獲取到繪圖句柄和布局控制對象,代碼如下:
- public Canvas getGraphics() {
- return canvas;
- }
- public Paint getPaint() {
- return paint;
- }
- public Rect getClip() {
- return clip;
- }
- public Bitmap getBitmap() {
- return bitmap;
- }
#p#
3:Connector類
OPhone平臺下不再提供Connector這個靜態(tài)接口類,而是提供HttpURLConnection以及URL進行網絡通訊,值得注意的是HttpURLConnection對象必須調用setDoOutput(true)方法以允許打開輸出流對象,調用setDoInput(true)方法以打開輸入流對象,代碼封裝如下:
- import java.net.HttpURLConnection;
- import java.net.URL;
- public class Connector {
- public static final int READ = 1;
- public static final int WRITE = 2;
- public static final int READ_WRITE = 3;
- private static String platform;
- private static boolean j2me;
- public static HttpConnection open(String name,int mode,
- boolean timeouts)
- throws java.io.IOException
- {
- URL url = new URL(name);
- HttpURLConnection conn =
- (HttpURLConnection) url.openConnection();
- // Allow Inputs
- conn.setDoInput(true);
- // Allow Outputs
- conn.setDoOutput(true);
- conn.setConnectTimeout(100000);
- HttpConnection co = new HttpConnection();
- co.setConnEx(conn);
- return co;
- }
- }
4:HttpConncetion類
J2ME應用程序利用HttpConnection進行HTTP通訊,而OPhone平臺采用HttpURLConncetion進行HTTP通訊,這兩個不同平臺的通訊類接口基本上保持了一致,比如設置HTTP頭屬性以及獲取屬性的代碼:
- public void setRequestProperty(String field,String newValue)
- throws java.io.IOException
- {
- connEx.setRequestProperty(field, newValue);
- }
- public String getHeaderField(String key)
- {
- int temp = connEx.getHeaderFieldInt(key, 1024);
- return String.valueOf(temp);
- }
但是OPhone平臺下獲取HTTP包長度的接口名稱有所不同,在J2ME平臺下是通過getLength()方法獲取的,而OPhone平臺下通過getContentLength()方法獲取,這樣命名似乎更加合理。
上面筆者利用OPhone平臺的接口封裝了J2ME下常用的接口,基本上利用這些接口,J2ME程序可以無縫運行在OPhone平臺上。
J2ME適配包之按鍵映射
上節(jié)《J2ME適配包之界面》中講解了利用OPhone的接口規(guī)范開發(fā)J2ME的適配包,從而解決了J2ME程序無法運行在OPhone平臺上的技術難題,但是OPhone平臺和J2ME另一個重要的不同點就是按鍵響應接口,如何讓J2ME程序的按鍵響應可以無縫的移植到OPhone上呢?答案也是適配包。
筆者這里通過按鍵映射把OPhone平臺的鍵盤碼轉換成J2ME平臺的鍵盤碼,或者把OPhone平臺的觸摸事件通過代理傳遞給J2ME中相應的接口。
首先,需要在OPhone平臺下定義J2ME平臺GameCanvas類常用的鍵值碼,這些鍵值碼對應J2ME平臺下常用的按鍵:
- public class GameCanvas extends Screen {
- public static final int UP = 1;
- public static final int DOWN = 6;
- public static final int LEFT = 2;
- public static final int RIGHT = 5;
- public static final int FIRE = 8;
- public static final int GAME_A = 9;
- public static final int GAME_B = 10;
- public static final int GAME_C = 11;
- public static final int GAME_D = 12;
- public static final int KEY_NUM0 = 48;
- public static final int KEY_NUM1 = 49;
- public static final int KEY_NUM2 = 50;
- public static final int KEY_NUM3 = 51;
- public static final int KEY_NUM4 = 52;
- public static final int KEY_NUM5 = 53;
- public static final int KEY_NUM6 = 54;
- public static final int KEY_NUM7 = 55;
- public static final int KEY_NUM8 = 56;
- public static final int KEY_NUM9 = 57;
- public static final int KEY_STAR = 42;
- public static final int KEY_POUND = 35;
- }
當然,為了適配J2ME平臺的按鍵以及觸摸響應,筆者還定義了如下接口,在接收到相應的按鍵或者觸摸消息時,會轉發(fā)給相應的J2ME視圖,代碼如下:
- protected abstract void keyRepeated(int keyCode);
- protected abstract void keyPressed(int keyCode);
- protected abstract void keyReleased(int keyCode);
- protected abstract void pointerPressed(int x, int y);
- protected abstract void pointerReleased(int x, int y)
- protected abstract void pointerDragged(int x, int y);
上面定義的都是J2ME的視圖類Canvas里的按鍵以及觸摸響應接口,這里需要做的工作就是把OPhone里的按鍵進行映射轉換成J2ME里的標準鍵值后傳遞給這些接口,并由繼承自GameCanvas的界面類進行實現,完全和J2ME里一樣了,基本不用修改任何代碼。
OPhone下進行鍵值映射首先需要對接收到的按鍵消息KeyEvent對象進行預處理,然后轉換成上面定義的J2ME平臺下的標準鍵值并傳遞給GameCanvas對象,代碼如下:
- public int keyActual = 0;
- public int keyAction = 0;
- public void keyPreparse(int keyCode,KeyEvent e)
- {
- if(keyCode == KeyEvent.KEYCODE_0)
- {
- keyActual = GameCanvas.KEY_NUM0;
- }else if(keyCode == KeyEvent.KEYCODE_1)
- {
- keyActual = GameCanvas.KEY_NUM1;
- }else if(keyCode == KeyEvent.KEYCODE_2)
- {
- keyActual = GameCanvas.KEY_NUM2;
- }else if(keyCode == KeyEvent.KEYCODE_3)
- {
- keyActual = GameCanvas.KEY_NUM3;
- }else if(keyCode == KeyEvent.KEYCODE_4)
- {
- keyActual = GameCanvas.KEY_NUM4;
- }else if(keyCode == KeyEvent.KEYCODE_5)
- {
- keyActual = GameCanvas.KEY_NUM5;
- }else if(keyCode == KeyEvent.KEYCODE_6)
- {
- keyActual = GameCanvas.KEY_NUM6;
- }else if(keyCode == KeyEvent.KEYCODE_7)
- {
- keyActual = GameCanvas.KEY_NUM7;
- }else if(keyCode == KeyEvent.KEYCODE_8)
- {
- keyActual = GameCanvas.KEY_NUM8;
- }else if(keyCode == KeyEvent.KEYCODE_9)
- {
- keyActual = GameCanvas.KEY_NUM9;
- }else if(keyCode == KeyEvent.KEYCODE_POUND)
- {
- keyActual = GameCanvas.KEY_POUND;
- }else if(keyCode == KeyEvent.KEYCODE_STAR)
- {
- keyActual = GameCanvas.KEY_STAR;
- }else if(keyCode == KeyEvent.KEYCODE_DPAD_UP)
- {
- keyActual = GameCanvas.UP;
- keyAction = GameCanvas.UP;
- }else if(keyCode == KeyEvent.KEYCODE_DPAD_DOWN)
- {
- keyActual = GameCanvas.DOWN;
- keyAction = GameCanvas.DOWN;
- }else if(keyCode == KeyEvent.KEYCODE_DPAD_LEFT)
- {
- keyActual = GameCanvas.LEFT;
- keyAction = GameCanvas.LEFT;
- }else if(keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)
- {
- keyActual = GameCanvas.RIGHT;
- keyAction = GameCanvas.RIGHT;
- }else if(keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
- {
- keyActual = GameCanvas.FIRE;
- keyAction = GameCanvas.FIRE;
- }
- else if(keyCode == KeyEvent.KEYCODE_SOFT_LEFT)
- {
- keyActual = Globe.softKeyLeft;
- }
- else if(keyCode == KeyEvent.KEYCODE_SOFT_RIGHT)
- {
- keyActual = Globe.softKeyRight;
- }
- }
筆者對OPhone里常用的鍵1~9以及*、#、上下左右、確定鍵進行了映射,映射后為標準J2ME下的鍵值,其中keyActual變量保存的即為J2ME平臺下的標準鍵值,接下來只需要把這些鍵值傳遞給原來J2ME里的接口即可,代碼如下:
- public boolean onTouchUp(MotionEvent e) {
- if (isEvent) {
- return isEvent;
- }
- pointerReleased((int)(e.getX()),(int)(e.getY()));
- return isEvent;
- }
- public boolean onTouchDown(MotionEvent e) {
- if (isEvent) {
- return isEvent;
- }
- pointerPressed((int)(e.getX()),(int)(e.getY()));
- return isEvent;
- }
- public boolean onKeyDown(int keyCode, KeyEvent e) {
- keyPreparse(keyCode,e);
- keyPressed(keyActual);
- return true;
- }
- public boolean onKeyUp(int keyCode, KeyEvent e) {
- keyPreparse(keyCode,e);
- keyReleased(keyActual);
- return true;
- }
onTouchUp()方法、onTouchDown()方法對觸摸事件進行映射處理,把處理后的鍵值傳遞給pointerPressed()方法,pointerPressed()方法再派發(fā)給相應的GameCanvas視圖,onKeyDown()方法、onKeyUp()方法是對按鍵事件進行的處理,當然,如果有必要還可以實現getGameAction()方法,代碼如下:
- public int getKeyStates()
- {
- return keyActual;
- }
- public int getGameAction(int keyCode)
- {
- return keyAction;
- }
#p#
J2ME適配包之數據持久存儲
筆者在前面兩節(jié)《J2ME適配包之界面》、《J2ME適配包之按鍵映射》中分別講了無縫移植J2ME程序到OPhone平臺上對界面和用戶按鍵交互所做的適配接口,原則上利用這些接口原有的J2ME程序基本不用做任何的修改就可以運行在OPhone平臺上。
筆者本節(jié)要講述的是J2ME平臺和OPhone平臺另外一個重要的不同點,那就是數據持久存儲系統。
J2ME平臺里采用RMS系統進行數據的持久存儲,而OPhone平臺則提供了豐富的接口進行數據的持久存儲,但任何持久存儲的本質無非就是數據串行化后被保存到磁盤空間上,仔細研究J2ME平臺RMS系統的實現源碼可以看到,J2ME是通過一個叫做RecordStoreFile的類進行數據持久化存儲的,而這個RecordStoreFile類封裝IO對數據進行操作存儲的。
由此可見,RMS系統也是通過IO把數據串行化后存儲應用程序的空間內的,絕大多數的J2ME程序都需要利用RMS來進行出具的持久存儲的,比如游戲積分、系統設置等。那么為了無縫移植J2ME到OPhone平臺,筆者這里自己寫了一個簡易的RMS系統,能滿足絕大多數應用程序的需要。
相對于RMS系統,筆者更喜歡SQLite套件,這個開源的數據庫軟件使用起來極為方便并未已經為很多平臺支持,比如iPhone OS支持的SQLite3套件,更多關于SQLite的信息可以在http://www.sqlite.org/ 網站上找到,這個開源數據庫軟件是一種文件型數據庫,一個數據庫就對應一個文件,無需安裝數據庫服務器端軟件。OPhone也同樣支持SQLite套件,而且使用起來也非常方便,為開發(fā)者進行數據持久存儲提供了更方便的途徑,但是限于筆者這篇文章集中于無縫移植J2ME應用程序到OPhone平臺,所以這里就不再累述,有興趣的讀者可以查找相關的資料。
OPhone平臺下對文件的操作和J2ME基本一樣,但是需要綁定一個Context上下文,以把文件保存到當前應用程序的目錄下,這個目錄在打開DDMS窗口后可以看到,具體位置是data/data/PACKAGE_NAME/files下面。
利用文件操作可以進行數據的讀寫,其中System.getSystemHandler().getContext()方法為筆者自己定義的全局靜態(tài)方法,用以保存當前應用程序的Context上下文。利用文件進行數據讀寫操作的代碼如下:
- public String read(String file) {
- String data = "";
- try {
- FileInputStream stream =
- System.getSystemHandler().getContext().openFileInput(file);
- StringBuffer sb = new StringBuffer();
- int c;
- while ((c = stream.read()) != -1) {
- sb.append((char) c);
- }
- stream.close();
- data = sb.toString();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return data;
- }
- public void write(String file, byte[] msg) {
- try {
- FileOutputStream stream =
- System.getSystemHandler().getContext().openFileOutput(file,
- Context.MODE_WORLD_WRITEABLE);
- stream.write(msg);
- stream.flush();
- stream.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
有了基本的讀寫數據操作,就可以封裝RMS中常用的key-value的保存和讀取了,因為RMS系統支持的是字節(jié)流的讀寫,所以筆者這里的value都為字節(jié)數組byte[],讀者可以根據自己的需要進行功能擴展,代碼實現如下:
- public boolean put(String key, byte[] value) {
- boolean bSaveOk = false;
- this.searchKey = key;
- byte[] data = null;
- if (value == null) {
- throw new NullPointerException();
- }
- ByteArrayOutputStream bout = null;
- DataOutputStream dout = null;
- try {
- bout = new ByteArrayOutputStream();
- dout = new DataOutputStream(bout);
- dout.writeUTF(key);
- dout.writeInt(value.length);
- dout.write(value, 0, value.length);
- data = bout.toByteArray();
- write(dbName,data);
- bSaveOk = true;
- } catch (Exception e) {
- bSaveOk = false;
- e.printStackTrace();
- }
- closeDb();
- return bSaveOk;
- }
- public byte[] getByteArray(String key) {
- ByteArrayInputStream bin = null;
- DataInputStream din = null;
- byte[] data = null;
- try {
- String valueKey = read(dbName);
- din = new DataInputStream(
- new ByteArrayInputStream(valueKey.getBytes()));
- while(din.available() > 0)
- {
- String getKey = din.readUTF();
- int getLength = din.readInt();
- data = new byte[getLength];
- int bytesRead = 0;
- while (bytesRead < data.length) {
- int count = din.read(data, bytesRead, data.length
- - bytesRead);
- if (count == -1)
- break;
- bytesRead += count;
- }
- if(getKey.equals(key))
- break;
- }
- din.close();
- din = null;
- } catch (Exception e) {
- e.printStackTrace();
- data = null;
- }
- closeDb();
- return data;
- }
- }
J2ME適配包總結
在上面的三個小節(jié)中,筆者分別總結了OPhone平臺下無縫移植J2ME應用程序需要修改的三個核心的地方:界面適配、按鍵映射以及數據持久存儲,基本上把這些代碼加入到原有的J2ME應用程序,重新導入這些自己實現的J2ME封裝類,原有的J2ME程序無需任何修改就可以無縫運行在OPhone平臺。