在寫JAVAME程序的時候,我們經常需要保存一些數據到手機裡面,也經常希望能把對象也保存到手機裡面,但是JAVAME裡面沒有反射機制,也沒有Java.io.Serializable接口,所以沒有序列化的機制,要保存對象的話,就得自己動手了。
在JAVAME中,程序的數據保存的地方,無外乎兩種,一種是把數據保存在RMS裡面,這是所有的JAVAME的手機都支持的,還有一種就是把數據保存在手機的文件系統裡面,這個不是所有手機都能支持的,只有支持JSR075的手機,才支持把數據保存在文件系統裡面,並且如果你的程序沒有經過簽名的話,你每次保存或者讀取,手機都會彈出惱人的提示,是否允許程序訪問文件系統。所在我一般都是把數據存在RMS裡面,因為讀寫RMS是安全的,並且也是不需要手機提示的。因為我們的RMS數據是存在一個特殊的地方。但是JavaME的RMS功能非常底層,為了保存一些數據,我們必須和byte[]打交道,所以我就產生了,在此之前封裝一層自己的程序的想法,這樣封裝好以後,使用起來就非常方便了。只要實現了相關接口,就可以享受到比較易用的方法了。
此框架總共包括了四個類,分別如下:
Serializable類,它是一個接口,類似於JAVASE裡面的Serializable接口,唯一不同的就是,JavaSE裡面的接口是一個空接口,只做標記用的,而這裡的這個接口是有方法需要實現的。
Lazy類,它也是一個接口,它定義了一些方法,如果你的對象比較大,需要惰性加載的時候,可以實現此接口,並且此接口是Serializable接口的子類,也就是說實現了Lazy接口,你就相當於實現了Serializable接口。
RMSUtil類,此類是一個工具類,用於統一進行RMS的相關操作,也是此框架的核心類。
RecordFetcher類,也是一個接口,它繼承了RecordComparator, RecordFilter接口,在取數據的時候,需要用到它。
好了,下面我們就開始看代碼吧。
1 /*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5 package com.hadeslee.mobile.rms;
6
7 import Java.io.IOException;
8
9 /**
10 * 一個可自己串行化的類所要實現的接口
11 * @author hadeslee
12 */
13 public interface Serializable {
14
15 /**
16 * 把自己編碼成字節數組的格式
17 * @return 字節數組
18 */
19 public byte[] serialize() throws IOException;
20
21 /**
22 * 把一個對象用此字節數組進行重裝
23 * @param data 字節數組
24 */
25 public void unSerialize(byte[] data) throws IOException;
26
27 /**
28 * 設置此對象序列化後對應的存儲對象的ID
29 * @param id ID
30 */
31 public void setId(int id);
32
33 /**
34 * 得到此對象序列化後的ID
35 * 此方法唯有在反序列化後的對象上調用才有效
36 * 如果一個對象是沒有序列化的,那麼它的ID是-1;
37 * @return ID
38 */
39 public int getId();
40 }
41
1 /*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5 package com.hadeslee.mobile.rms;
6
7 import Java.io.IOException;
8
9 /**
10 * 可以延遲加載的對象必須要實現的接口
11 * @author binfeng.li
12 */
13 public interface Lazy extends Serializable {
14
15 /**
16 * 實現此接口的類要實現的方法
17 * 可以用於延遲加載某些屬性。比如
18 * get("ImgData"),get("fullImage")..等等
19 * 由於J2ME不支持注釋也不支持反射,所以只能以
20 * 此種方法來進行模擬了
21 * 此方法是RMSUtil要存對象的時候調用的,這樣就可以把
22 * 一個對象的不同部份存到不同的RMS裡面去了
23 * @param key 要得到的某性的鍵
24 * @return 其對應的值
25 * @throws IOException
26 */
27 public byte[] getAttach(Object key)throws IOException;
28
29 /**
30 * 當把某個附屬的對象保存進去以後,所要調用的
31 * 方法,此方法告訴主體,它的那個附件被保存後
32 * 在RMS裡面對應的ID是多少
33 * @param key
34 * @param id
35 */
36 public void savedAttach(Object key, int id);
37
38 /**
39 * 得到此對象所支持的所有的key的數組
40 * @return KEY的數組,不能為NULL
41 */
42 public Object[] getAttachKeys();
43
44 /**
45 * 此對象的附屬對象所存的RMS的名字
46 * @return RMS的名字
47 */
48 public String getNameOfAttachRMS();
49 }
50
1 /*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5 package com.hadeslee.mobile.rms;
6
7 import Javax.microedition.rms.RecordComparator;
8 import Javax.microedition.rms.RecordFilter;
9
10 /**
11 * 此類是一個繼承了兩個接口的接口,並且添加了自己
12 * 的方法,自己的方法是用於通知數量以及開始取的位置
13 * 只是為了方便於傳遞參數以及以後擴展
14 * @author binfeng.li
15 */
16 public interface RecordFetcher extends RecordComparator, RecordFilter {
17
18 /**
19 * 從哪個下標開始取
20 * @return 下標
21 */
22 public int getFromIndex();
23
24 /**
25 * 最多取多少條記錄
26 * @return 記錄
27 */
28 public int getMaxRecordSize();
29 }
30
1 /*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5 package com.hadeslee.mobile.rms;
6
7 import com.hadeslee.mobile.log.LogManager;
8 import Java.util.Enumeration;
9 import Java.util.Hashtable;
10 import Java.util.Vector;
11 import Javax.microedition.rms.RecordEnumeration;
12 import Javax.microedition.rms.RecordStore;
13 import Javax.microedition.rms.RecordStoreException;
14
15 /**
16 * 一個專門用來操作RMS的工具類,通過這個類
17 * 可以把RMS封裝起來,上層調用就更方便了
18 * @author binfeng.li
19 */
20 public class RMSUtil {
21
22 /**
23 * 用於緩存生命周期之內的所有的RecordStore的表,當MIDlet要退出的
24 * 時候,調用此類的關閉方法,使RMS正確地被關閉
25 */
26 private static Hashtable rmsCache = new Hashtable();
27
28 private RMSUtil() {
29 }
30
31 /**
32 * 插入一個對象到一個RMS的數據庫裡面,如果此數據庫不存在
33 * 則自動創建一個對於MIDlet私有的數據庫。如果存在,則直接
34 * 插在此數據庫的最後面
35 * @param ser 要插入的數據,必須是實現了Serializable接口的類
36 * @return 是否插入成功
37 */
38 public static boolean insertObject(Serializable ser) {
39 RecordStore rs = null;
40 try {
41 rs = getRecordStore(ser.getClass().getName());
42 if (ser instanceof Lazy) {
43 Lazy lazy = (Lazy) ser;
44 insertAttachDatas(lazy);
45 }
46 byte[] data = ser.serialize();
47 int id = rs.addRecord(data, 0, data.length);
48 ser.setId(id);
49 return true;
50 } catch (Exception exe) {
51 exe.printStackTrace();
52 LogManager.error("RMSUtil.insertObject(),ser = " + ser + ",exe = " + exe);
53 return false;
54 }
55 }
56
57 /**
58 * 更新某個對象到RMS裡面去,
59 * @param ser 要更新的對象
60 * @return 是否成功
61 */
62 public static boolean updateObject(Serializable ser) {
63 RecordStore rs = null;
64 try {
65 rs = getRecordStore(ser.getClass().getName());
66 byte[] data = ser.serialize();
67 rs.setRecord(ser.getId(), data, 0, data.length);
68 return true;
69 } catch (Exception exe) {
70 exe.printStackTrace();
71 LogManager.error("RMSUtil.updateObject(),ser = " + ser + ",exe = " + exe);
72 return false;
73 }
74 }
75
76 /**
77 * 從RMS裡面刪除某個對象
78 * @param ser 要刪除的對象
79 * @return 是否成功
80 */
81 public static boolean deleteObject(Serializable ser) {
82 if (ser.getId() == -1) {
83 return false;
84 }
85 RecordStore rs = null;
86 try {
87 rs = getRecordStore(ser.getClass().getName());
88 int id = ser.getId();
89 rs.deleteRecord(id);
90 ser.setId(-1);
91 return true;
92 } catch (Exception exe) {
93 exe.printStackTrace();
94 LogManager.error("RMSUtil.deleteObject(),ser = " + ser + ",exe = " + exe);
95 return false;
96 }
97 }
98
99 /**
100 * 從某個數據庫裡面讀取某個對象
101 * @param id 此對象的ID
102 * @param clz 對應的類
103 * @return 此對象,如果發生任何異常,則返回null
104 */
105 public static Serializable readObject(int id, Class clz) {
106 RecordStore rs = null;
107 try {
108 rs = getRecordStore(clz.getName());
109 byte[] data = rs.getRecord(id);
110 Serializable ser = (Serializable) clz.newInstance();
111 ser.unSerialize(data);
112 ser.setId(id);
113 return ser;
114 } catch (Exception exe) {
115 //如果讀取對象失敗,則可能是有東西被刪了或者版本不一樣,此時就應該刪掉
116 exe.printStackTrace();
117 LogManager.error("RMSUtil.readObject(),id = " + id + ",Class = " + clz + ",exe= " + exe);
118 if (rs != null) {
119 try {
120 rs.deleteRecord(id);
121 } catch (Exception ex) {
122 ex.printStackTrace();
123 LogManager.error("RMSUtil.readObject$rs.deleteRecord(id),id = " + id + ",exe = " + ex);
124 }
125 }
126 return null;
127 }
128 }
129
130 /**
131 * 得到某個類存在RMS裡面的總數,這樣便於分段取
132 * @param cls 類名
133 * @return 有效記錄總數
134 */
135 public static int getStoreSize(Class cls) {
136 try {
137 RecordStore rs = getRecordStore(cls.getName());
138 return rs.getNumRecords();
139 } catch (Exception exe) {
140 exe.printStackTrace();
141 LogManager.error("RMSUtil.getStoreSize(),Class = " + cls + ",exe = " + exe);
142 return -1;
143 }
144 }
145
146 /**
147 * 列出某個類的對象的集合,最多取多少個對象
148 * @param cls 類名
149 * @param from 從第幾個開始取
150 * @param maxSize 最多取多少個對象
151 * @return 取到的列表
152 */
153 public static Vector listObjects(Class cls, int from, int maxSize) {
154 System.out.println("class="+cls);
155 if (from < 0 || maxSize < 1) {
156 throw new IllegalArgumentException("from can not less than 0 and maxSize must greater than 0");
157 }
158 Vector v = new Vector();
159 RecordEnumeration ren = null;
160 try {
161 RecordStore rs = getRecordStore(cls.getName());
162 ren = rs.enumerateRecords(null, null, false);
163 fetchRecord(v, cls, ren, from, maxSize);
164 } catch (Exception exe) {
165 LogManager.error("RMSUtil.listObjects(),Class = " + cls + ",from = " + from + ",maxSize = " + maxSize + ",exe = " + exe);
166 exe.printStackTrace();
167 } finally {
168 ren.destroy();
169 }
170 return v;
171 }
172
173 /**
174 * 用於前面一個方法和後面一個方法的共用方法,
175 * 它用來從特定的記錄枚舉裡面去取特定的記錄,
176 * 並放到特定的地方
177 * @param v 要保存的地方
178 * @param cls 要實例化的類
179 * @param ren 記錄的枚舉
180 * @param from 從哪裡開始取
181 * @param maxSize 要取多少條記錄
182 * @throws Java.lang.Exception 可能會拋出的異常
183 */
184 private static void fetchRecord(Vector v, Class cls, RecordEnumeration ren, int from, int maxSize) throws Exception {
185 int index = 0;
186 int size = 0;
187 while (ren.hasNextElement()) {
188 int id = ren.nextRecordId();
189 if (index >= from) {
190 if (size < maxSize) {
191 Serializable ser = readObject(id, cls);
192 if (ser != null) {
193 v.addElement(ser);
194 size++;
195 }
196 } else {
197 break;
198 }
199 }
200 index++;
201 }
202 }
203
204 /**
205 * 列出某個類的對象,並用一種過濾以及排序的方法來進行過濾或者排序
206 * @param cls 類名
207 * @param fetcher 取記錄的方法
208 * @return 記錄列表
209 */
210 public static Vector listObjects(Class cls, RecordFetcher fetcher) {
211 System.out.println("fetcher class="+cls);
212 int from = fetcher.getFromIndex();
213 int maxSize = fetcher.getMaxRecordSize();
214 if (from < 0 || maxSize < 1) {
215 throw new IllegalArgumentException("from can not less than 0 and maxSize must greater than 0");
216 }
217 Vector v = new Vector();
218 RecordEnumeration ren = null;
219 try {
220 RecordStore rs = getRecordStore(cls.getName());
221 ren = rs.enumerateRecords(fetcher, fetcher, false);
222 fetchRecord(v, cls, ren, from, maxSize);
223 } catch (Exception exe) {
224 LogManager.error("RMSUtil.listObjects(),Class = " + cls + ",exe = " + exe);
225 exe.printStackTrace();
226 } finally {
227 ren.destroy();
228 }
229 return v;
230 }
231
232 /**
233 * 插入某個可延遲加載的對象的所有附件到數據庫裡面去
234 * 插入完成後,此lazy對象將變得很完整,因為此時它的
235 * 附件對象的ID都已經設置好了
236 * @param lazy 要插入附件的主對象
237 * @return 是否插入成功
238 */
239 private static boolean insertAttachDatas(Lazy lazy) {
240 try {
241 Object[] attachKeys = lazy.getAttachKeys();
242 RecordStore rs = getRecordStore(lazy.getNameOfAttachRMS());
243 for (int i = 0; i < attachKeys.length; i++) {
244 Object key = attachKeys[i];
245 byte[] data = lazy.getAttach(key);
246 int id = rs.addRecord(data, 0, data.length);
247 lazy.savedAttach(key, id);
248 }
249 return true;
250 } catch (Exception exe) {
251 exe.printStackTrace();
252 LogManager.error("RMSUtil.insertAttachDatas(),Lazy = " + lazy + ",exe = " + exe);
253 return false;
254 }
255 }
256
257 /**
258 * 得到某個可以延遲加載的對象的某個附件的字節數組內容
259 * TODO 如果能把此方法變成私有,那就更好了
260 * 此方法是專門供lazy對象調用的,這樣的話,實體類裡面就出現了
261 * 讀取數據的方法,但是由於J2ME不支持反射,只能這樣實現了
262 * @param lazy 可以延遲加載的對象
263 * @param id 附件的ID
264 * @return 對應的數組
265 */
266 public static byte[] getAttachData(Lazy lazy, int id) {
267 try {
268 return getRecordStore(lazy.getNameOfAttachRMS()).getRecord(id);
269 } catch (Exception exe) {
270 exe.printStackTrace();
271 LogManager.error("RMSUtil.getAttachData(),Lazy = " + lazy + ",id = " + id + ",exe = " + exe);
272 return null;
273 }
274 }
275
276 /**
277 * 更新某個對象的附件
278 * TODO 如果能把此方法變成私有就更好了
279 * @param lazy 可延遲加載的對象
280 * @param id 附件的ID
281 * @param data 附件的內容
282 * @return 是否成功
283 */
284 public static boolean updateAttachData(Lazy lazy, int id, byte[] data) {
285 try {
286 RecordStore rs = getRecordStore(lazy.getNameOfAttachRMS());
287 rs.setRecord(id, data, 0, data.length);
288 return true;
289 } catch (Exception exe) {
290 exe.printStackTrace();
291 LogManager.error("RMSUtil.updateAttachData(),Lazy = " + lazy + ",exe = " + exe);
292 return false;
293 }
294 }
295
296 /**
297 * 從附件數據庫中刪除某個附件
298 * @param lazy 主對象
299 * @param id 附件的ID
300 * @return 是否刪除成功
301 */
302 public static boolean deleteAttachData(Lazy lazy, int id) {
303 try {
304 RecordStore rs = getRecordStore(lazy.getNameOfAttachRMS());
305 rs.deleteRecord(id);
306 return true;
307 } catch (Exception exe) {
308 exe.printStackTrace();
309 LogManager.error("RMSUtil.deleteAttachData(),Lazy = " + lazy + ",id = " + id + ",exe = " + exe);
310 return false;
311 }
312 }
313
314 /**
315 * 關閉所有的RMS
316 */
317 public static void closeAllRMS() {
318 Enumeration en = rmsCache.elements();
319 while (en.hasMoreElements()) {
320 RecordStore rs = (RecordStore) en.nextElement();
321 closeRecordStore(rs);
322 }
323 rmsCache.clear();
324 }
325
326 public static void deleteRecord(Class cls, int id) {
327 deleteRecord(cls.getName(), id);
328 }
329
330 /**
331 * 刪除某個倉庫裡面的某條記錄
332 * @param rsName 倉庫的名字
333 * @param id 記錄的ID
334 */
335 public static void deleteRecord(String rsName, int id) {
336 try {
337 RecordStore rs = RecordStore.openRecordStore(rsName, false);
338 if (rs != null) {
339 rs.deleteRecord(id);
340 }
341 rs.closeRecordStore();
342 } catch (Exception exe) {
343 }
344 }
345
346 /**
347 * 一個簡單的方法用於關閉RecordStore
348 * @param rs 要關閉的RecordStore
349 */
350 private static void closeRecordStore(RecordStore rs) {
351 try {
352 rs.closeRecordStore();
353 } catch (Exception exe) {
354 LogManager.error("RMSUtil.closeRecordStore(),rs = " + rs + ",exe = " + exe);
355 exe.printStackTrace();
356 }
357 }
358
359 /**
360 * 得到某個RMS的存儲數據庫,先從緩存裡面去找,如果沒有找到則生成
361 * 一個並把它放到緩存裡面,還有,因為RMS的名字,最長只支持32位
362 * @param name 數據庫的名字
363 * @return 找到的RMS數據庫
364 */
365 private synchronized static RecordStore getRecordStore(String name) throws RecordStoreException {
366 if (name.length() > 32) {
367 name = name.substring(name.length()-32, name.length());
368 }
369 if (rmsCache.containsKey(name)) {
370 return (RecordStore) rmsCache.get(name);
371 } else {
372 RecordStore rs = RecordStore.openRecordStore(name, true);
373 rmsCache.put(name, rs);
374 return rs;
375 }
376 }
377 }
378
相信看完代碼以後,大家應該知道如何使用它吧。如果有需要持久化的類,那麼就需要實現Serializable接口,然後只要調用RMSUtil.insertObject()就可以了,同理,查找也是一樣的,你可以查找同一個類的一系列對象,也可以自己定義記錄查詢器,在裡面設置查詢條件。
目前JAVAME的持久化框架,也有用其它的方法實現的,比如動態插入代碼的方法,也就是你在寫好了JavaME的代碼以後,在編譯的過程中,它自動幫你加上相應的方法,我看了一個他們的源碼,其實也就是它們自己幫你實現了一個相當於Serializable接口,我覺得這樣不好的地方在於,它會為你的類添加方法,萬一你的類裡面原來就有那個方法的時候,那就會出現不可意料的情況了,還有,我覺得自己的數據還是自己一個一個把它寫出來,這樣心裡更踏實一些。我一直都認為,封裝得有一個度,不能過度的封裝,過度封裝表面上看是編碼更方便了,但是寫的時候,自己心裡也更沒底了,因為你不知道別人的代碼都做了一些什麼。因為別人的代碼做的事情太多了。呵呵,純屬個人意見。
大家如果有什麼自己的看法,歡迎留言。
還有,此代碼用到了我的另外一個通用框架,那就是LOG框架,所以如果直接下載的話,可能會編譯不過了,只要注釋掉LogManager的調用就可以了。LOG框架的說明點擊這裡,這個LOG框架現在正在改動中,以使它更好用,更沒有侵入性。