BlackBerry 应用程序开发者指南 第二卷:高级--第8章 存储持久数据


作者:Confach 发表于 2006-04-28 22:28 pm
版权信息:可以任意转载, 转载时请务必以超链接形式标明文章原始出处 和作者信息.
http://www.cnblogs.com/confach/articles/387952.html

8

                  第8 存储持久数据


持久数据选项

管理持久数据

内存管理以及持久对象

管理客户对象

持久数据选项

BlackBerry设备上,可以通过下面的方法来存储数据:

  • 使用MIDP记录存储.
  • 使用BlackBerry持久存储模型.

如果想让你的应用程序可以在多个与Java ME兼容的设备上运行,那么采用MIDP的实现.如果你编写的应用程序仅仅在BlackBerry设备运行,使用BlackBerry持久存储模型,因为它提供了一个更为灵活有效的方式来存储数据,

MIDP存储记录

javax.microedition.rms包提供了MIDP记录存储的实现.持久数据存储在RecordStore对象里.一个记录存储最大可以为64KB.

数据的离散单元称为记录.一个记录是一个字节数组,赋给它一个唯一标志数.

创建一个记录存储

调用openRecordStore().指定true来描述当记录存储不存在时应该创建此记录存储.

RecordStore store = RecordStore.openRecordStore("Contacts", true);

:当从BlackBerry设备删除一个应用程序时,所有此应用程序创建的记录存储都会删除.每个在MIDlet(suite)的记录存储都有一个唯一名.MIDlet仅可以访问一个在相同包里的由MIDlet创建的记录存储.

增加一个记录

调用addRecord().

int id = store.addRecord(_data.getBytes(), 0, data.length());

 

获取一个记录

调用getRecord(int, byte[], int).给本方法提供一个记录ID,字节数组,以及一个偏移作为参数.

byte[] data = new byte[store.getRecordSize(id)];

store.getRecord(id, data, 0);

String dataString = new String(data);

 

获取所有记录

打开存储,然后获取其迭代.

RecordStore store = RecordStore.openRecordStore("Contacts", false);

RecordEnumeration e = store.enumerateRecords(null, null, false);

enumerateRecords(RecordFilter filter, RecordComparator comparator, Boolean keepUpdated) 方法有如下参数:

参数

描述

filter

此参数指定一个RecordFilter对象获取记录存储结果的子集(如果为null,将返回所有记录存储).

comparator

此参数指定一个RecordComparator对象决定返回记录所在的顺序位置(如果为null.将返回无序的记录).

keepUpdated

此参数决定对于记录存储,迭代保持当前的改变.

BlackBerry持久存储

MIDP中的记录存储(RecordStore)BlackBerry持久模型(PersistentStore)2处主要的区别.

特性

描述

数据存储

MIDP记录仅以字节数组存储数据.相比之下,BlackBerry API允许你在持久存储中保存任何对象.这样,查询存储数据就会比记录模型更快一些.为了存储一个自定义的对象类型,自定义类型的类必须要实现Persistable接口.

数据共享

MIDP,每个RecordStore属于单个MIDlet,并且MIDlet也只能访问由相同包的MIDlet创建的记录存储.尽管如此,BlackBerry持久模型中,数据可以在应用程序之间共享,在创建数据的离散应用程序中共享.代码签名指定只有被认证的应用程序才可以访问这些数据.

:BlackBerry持久性API在手持设备软件3.6或后期版本可用.对于早期的版本,你必须使用MIDP记录存储.

保留存储空间

BlackBerry的存储空间是有限的.你应该小心设计你的程序,将需要存储持久数据的闪存数量最小化.

在一般的BlackBerry,对于一个标准的BlackBerry应用程序,不需要的存储空间必须在所有应用程序之间共享,用来存储用户数据,包含日历约会,联系人,以及消息.

如果BlackBerry设备在一个小内存情况下操作,它有可能完成下面的动作释放内存空间:

  • BlackBerry上删除以前的消息.
  •  BlackBerry 设备上删除超过一个星期的日历约会(如果启动了无线日历同步).

如果因低内存而BlackBerry设备删除了消息或者日历约会,那么在桌面消息程不会删除数据.为获的更多信息,参看96页的内存管理以及持久对象”.

   :用户点击设备选项的Status可以查看当前可用的数据空间.

 

备份与恢复

net.rim.device.api.synchronization包中,同步(synchronization)API允许你备份以及恢复BlackBerry设备上的持久数据.为获取更多信息,参看104页的增加支持备份持久数据”.

 

安全

缺省的,BlackBerry上由RIM数字签名的应用程序可以访问持久存储上的数据,联系RIM获取关于控制数据访问的信息.

 

管理工具

BES 3.5 Microsoft® Exchange SP2 BES 2.2 M® Lotus® Domino®,系统管理员可以使用IT策略控制第三方应用程序访问持久存储.

管理员可以设置应用程序控制项ALLOW_USE_PERSISTENT_STORETRUEFALSE.缺省的,第三方应用程序可以使用持久存储(ALLOW_USE_PERSISTENT_STORETRUE)

:这个策略对MIDP记录存储没有影响.

数据完整性

为了维护持久存储数据的完整性,如果在提交时发生一个错误,不会更新局部.

:因低内存VM完成一个紧要的垃圾回收时,数据完整性会折中.在这样的情况下,BlackBerry设备提交时,部分完成的事务会提交.在正常垃圾回收下未提交的事务不会提交.

管理持久数据

持久数据类型

如果一个自定义数据类型类实现了Persistable类接口,那么此数据类型可以持久保存.小面原生数据类型也可以持久存储.

  • java.lang.Boolean
  • java.lang.Byte
  • java.lang.Character
  • java.lang.Integer
  •  java.lang.Long
  • java.lang.Object
  • java.lang.Short
  • java.lang.String
  • java.util.Vector
  • java.util.Hashtable
:当你持久化一个对象时,此对象引用的任何对象也可以持久化.

创建一个持久化数据库

每个应用程序一般可以创建单个PersistentObject.此对象是应用程序的持久化数据以及索引的根数据库.应用程序将保存数据到此PersistentObject.

:使用一个静态的构造子,这样PersistentObject只创建一次,即此类的一个对象第一次创建时.每次一个进程开始时,它包含的静态块再一次运行.

一个唯一的long键标志了每个PersistentObject.此键一般为一个全权包名的哈希

 :当一个应用程序从BlackBerry删除时,所有此应用程序创建的持久化对象也将删除.

创建一个唯一的long

  1.        BlackBerry IDE,输入一个字符串,例如com.rim.samples.docs.userinfo.

  2.         选择此字符串.

  3.         右击,然后单击Convert 'com.rim.samples.docs.userinfo' to long. long值将会出现.
  :在你的代码加入注释表明用来生成long键的字符串..

static PersistentObject store;

static {

    store = PersistentStore.getPersistentObject( 0xa1a569278238dad2L );

}

持久存储数据

为了将数据保存到持久存储里,调用PersistentObject上的setContents().此方法用新的值替代已存在的值.调用commit()保存到持久存储里,
  :如果在提交的过程中发生一个错误,已经完成的更新不会提交.PersistentObject里的数据从最后一次提交中获取值,以保持数据的完整性.

String[] userinfo = {username, password};

synchronized(store) {

    store.setContents(userinfo);

    store.commit();

}

如果你有许多对象需要提交到存储里,你可以以一个批事务的形式提交它们.为了实现此,调用PersistentStore.getSynchObject()方法获取持久存储监视器紧锁对象.然后同步对象,如果必要,调用commit()方法.当你释放监视对象的同步时,你的所有事务一次性被提交.如果批处理有任何提交失败,整个批提交也失败.当你同步监视器对象时,如果你调用forceCommit(),这个对象立即提交,并且它不再时批事务中的一部分了.

获取持久数据

调用 PersistentObject 上的getContents() 方法.

PersistentObject.getContents()返回的对象显式的转化为你需要的类型.

synchronized(store) {

    String[] currentinfo = (String[])store.getContents();

    if(currentinfo == null) {

       Dialog.alert(_resources.getString(APP_ERROR));

       }

    else {

       currentusernamefield.setText(currentinfo[0]);

       currentpasswordfield.setText(currentinfo[1]);

       }

    }

}

 :当一个应用程序第一次访问数据据,它应该验证任何索引的顺序,如果出现一个问题,重新创建索引.应用程序应该能够识别并更正任何毁坏的或者丢失的数据的问题.为获得更对信息,参看91页的数据完整性”.

删除一个数据库

为了删除一个数据库,调用PersistentStore.destroyPersistentObject().提供一个PersistentObject的唯一键作为参数,
  : PersistentObject作为应用程序的根数据库使用.你删除它,你将删除所有此应用程序存储的持久化数据如果定义了一个PersistentStore.cod文件删除了,所有由此.cod文件创建的持久化对象也将删除.

为了删除单独的数据,把它们简单的当成普通数据看待,并删除这些数据的引用.垃圾数据会自动回收。

代码实例

本代码实例描述了如何为用户创建一个应用程序来查看它们当前的用户名和密码,输入一个新的用户名和密码,然后保存变化.


: UserInfo.java

/**

* UserInfo.java

* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.

*/

package com.rim.samples.docs.userinfo;

import net.rim.device.api.ui.*;

import net.rim.device.api.ui.component.*;

import net.rim.device.api.ui.container.*;

import net.rim.device.api.system.*;

import net.rim.device.api.util.*;

import java.util.*;

import net.rim.device.api.i18n.*;

import com.rim.samples.docs.baseapp.*;

import com.rim.samples.docs.resource.*;

 

public class UserInfo extends BaseApp

         implements UserInfoResource,KeyListener, TrackwheelListener {

    private static PersistentObject store;

    private static ResourceBundle _resources;

    private AutoTextEditField usernamefield;

    private PasswordEditField passwordfield;

    private AutoTextEditField currentusernamefield;

    private AutoTextEditField currentpasswordfield;

   

    static {

       _resources = ResourceBundle.getBundle(

              UserInfoResource.BUNDLE_ID, UserInfoResource.BUNDLE_NAME);

       store = PersistentStore.getPersistentObject(0xa1a569278238dad2L);

       }

   

    private MenuItem saveItem = new MenuItem( _resources.getString(MENUITEM_SAVE), 110, 10) {

       public void run() {

           String username = usernamefield.getText();

           String password = passwordfield.getText();

           String[] userinfo = {username, password};

           synchronized(store) {

              store.setContents(userinfo);

              store.commit();

              }

           Dialog.inform(_resources.getString(APP_SUCCESS));

           usernamefield.setText(null);

           passwordfield.setText(null);

           }

       };

      

      

       private MenuItem getItem = new MenuItem( _resources.getString(MENUITEM_GET), 110, 11 ) {

           public void run() {

              synchronized(store) {

                  String[] currentinfo = (String[])store.getContents();

                  if(currentinfo == null) {

                     Dialog.alert(_resources.getString(APP_ERROR));

                     }

                  else {

                     currentusernamefield.setText(currentinfo[0]);

                     currentpasswordfield.setText(currentinfo[1]);

                     }

                  }

              }

           };

          

           public static void main(String[] args) {

              UserInfo app = new UserInfo();

              app.enterEventDispatcher();

              }

          

           public UserInfo() {

              MainScreen mainScreen = new MainScreen();

              mainScreen.setTitle(new LabelField(

                     _resources.getString(APPLICATION_TITLE)));

              usernamefield = new AutoTextEditField(

                     _resources.getString(FIELD_NAME), "");

              passwordfield = new PasswordEditField(

                     _resources.getString(FIELD_PASSWORD),"");

              currentusernamefield = new AutoTextEditField(

                     _resources.getString(FIELD_CURRENTNAME), "");

              currentpasswordfield = new AutoTextEditField(

                     _resources.getString(FIELD_CURRENTPASSWORD),"");

              SeparatorField separator = new SeparatorField();

              mainScreen.add(usernamefield);

              mainScreen.add(passwordfield);

              mainScreen.add(separator);

              mainScreen.add(currentusernamefield);

              mainScreen.add(currentpasswordfield);

              mainScreen.addKeyListener(this);

              mainScreen.addTrackwheelListener(this);

              pushScreen(mainScreen);

              }

          

           public void makeMenu( Menu menu, int instance ) {

              menu.add(saveItem);

              menu.add(getItem);

              super.makeMenu(menu, 0);

              }

          

           public void onExit() {

              Dialog.alert(_resources.getString(APP_EXIT));

              }

           }


<!--[if !vml]-->

内存管理和持久化对象

BlackBerry设备上有固定数量的持久化对象句柄以及对象句柄.

闪存

持久化对象句柄

对象句柄

8 MB

12,000

24,000

16 MB

27,000

56,000

32 MB

65,000

132,000

BlackBerry设备上的每个持久化对象都会要求一个持久化对象句柄以及一个对象句柄.例如,一个记录包含了10String字段,那么它将要求11个对象句柄-一个为记录,一个为每个String.如果记录是持久化的,它将需要额外的11个持久化对象句柄.

使用下面的技术限制应用程序需要的持久化对象句柄数:

  • <!--[endif]-->如果可能,使用原始类型代替对象.一个原始类型,例如一个int或者一个char,它们不需要一个对象句柄.
  • 使用对象分组API (net.rim.device.api.system.ObjectGroup)将对象分组.一个分组的对象仅需要一个对象句柄.
:分组的对象是可读的.在做出改变之前调用ObjectGroup.expandGroup()撤销对象的分组.在完成改变之后,调用ObjectGroup.createGroup()将对象分组.必要时仅撤销对象的分组,当撤销对象分组时有性能将受到损耗,因为系统创建了分组对象的一个拷贝,并且将句柄分配给每个组里的对象.

为获取更多信息,参看BlackBerry JDE最佳内存实践白皮书.

管理自定义对象

创建一个数据库

创建一个Vector对象存储多个对象.创建一个PersistentObject作为应用程序的根数据库.

private static Vector _data;

PersistentObject store;

 

static {

    store = PersistentStore.getPersistentObject( 0xdec6a67096f833cL );

    //key is hash of test.samples.restaurants

    _data = (Vector)store.getContents();

    synchronized (store) {

       if (_data == null) {

           _data = new Vector();

           store.setContents(_data);

           store.commit();

           }

       }

    }

持久存储数据

可以持久化实现了Persistable接口的对象.

下面的代码实例作为inner类实现Persistable接口.它定义了一个带有4Object的数组来存储餐厅名,地址,电话号码,以及特色.并且定义了方法来获取和设置Object元素值.

 :一个类为了其对象,必须显式的实现Persistable接口来也就存储. 甚至其子类也适用这个需求.例如类A实现了Persistable接口,并且A有一个子类B,B的对象不能持久存储,除非B也实现Persistable接口.

private static final class RestaurantInfo implements Persistable {

private String[] _elements;

public static final int NAME = 0;

public static final int ADDRESS = 1;

public static final int PHONE = 2;

public static final int SPECIALTY = 3;

public RestaurantInfo() {

    _elements = new String[4];

    for ( int i = 0; i < _elements.length; ++i) {

       _elements[i] = new String("");

       }

}

 

public String getElement(int id) {

    return _elements[id];

}

 

public void setElement(int id, String value) {

    _elements[id] = value;

    }

 

}

创建扩展的对象

下面的方法允许你加入字段到对象中:

  • 在一个int,存储Boolean值做为bit.保留多余的位供将来使用.
  • 直接存储String,但是使用一个键/值对的VectorHashtable,这样附加的(或很少使用的)的字段也可以增加.
  •  如果你由一个表的索引,将他们存储在一个Vector或数组里,这样你可以增加未来的索引了.

保存一个对象

定义一个对象

下面的代码实例创建一个RestaurantInfo对象,并且使用它的设置方法来定义他的组件.

RestaurantInfo info = new RestaurantInfo();

info.setElement(RestaurantInfo.NAME, namefield.getText());

info.setElement(RestaurantInfo.ADDRESS,addressfield.getText());

info.setElement(RestaurantInfo.PHONE, phonefield.getText());

info.setElement(RestaurantInfo.SPECIALTY, specialtyfield.getText());

 

将一个对象加到一个Vector

调用addElement().

_data.addElement(info);

 

保存一个更新的对象

调用PersistentObjectsetContents(),然后调用commit()方法保存一个更新的对象

synchronized(store) {

    store.setContents(_data);

    store.commit();

}

: 当你做出改变时,同步一个持久对象,这样其他的线程在同一时间就不能做出改变.

获取一个对象

为了获取最近保存的对象,调用_data.lastElement().

public void run() {

synchronized(store) {

    _data = (Vector)store.getContents();

    if (!_data.isEmpty()) { RestaurantInfo info = (RestaurantInfo)_data.lastElement();

    namefield.setText(info.getElement(RestaurantInfo.NAME));

    addressfield.setText(info.getElement(RestaurantInfo.ADDRESS));

    phonefield.setText(info.getElement(RestaurantInfo.PHONE));

    specialtyfield.setText(info.getElement(

           RestaurantInfo.SPECIALTY));

     }

    }

}

代码实例

本实例描述了如何创建一个应用程序,它允许用户存储一个关于喜爱的餐厅信息.

本实例也允许用户保存多个餐厅的信息,并且可以查看最新保存的餐厅信息.


: Restaurants.java

/* Restaurants.java

* Copyright (C) 2004-2005 Research In Motion Limited.

*/

package com.rim.samples.docs.restaurants;

import net.rim.device.api.ui.*;

import net.rim.device.api.ui.component.*;

import net.rim.device.api.ui.container.*;

import net.rim.device.api.system.*;

import net.rim.device.api.util.*;

import java.util.*;

import net.rim.device.api.i18n.*;

import net.rim.blackberry.api.invoke.*;

import net.rim.blackberry.api.browser.*;

import com.rim.samples.docs.baseapp.*;

import com.rim.samples.docs.resource.*;

 

public class Restaurants extends BaseApp

             implements RestaurantResource,KeyListener, TrackwheelListener {

    private AutoTextEditField namefield;

    private AutoTextEditField addressfield;

    private EditField phonefield;

    private EditField websitefield;

    private EditField specialtyfield;

    private static Vector _data;

    private static PersistentObject store;

    private static ResourceBundle _resources;

    private MenuItem saveItem = new MenuItem(_resources.getString(MENUITEM_SAVE), 110, 10) {

       public void run() {

           RestaurantInfo info = new RestaurantInfo();            info.setElement(RestaurantInfo.NAME, namefield.getText());            info.setElement(RestaurantInfo.ADDRESS, addressfield.getText());            info.setElement(RestaurantInfo.PHONE, phonefield.getText());            info.setElement(RestaurantInfo.WEBSITE, phonefield.getText());            info.setElement(RestaurantInfo.SPECIALTY,                   specialtyfield.getText());            _data.addElement(info);            synchronized(store) {               store.setContents(_data);               store.commit();               }            Dialog.inform(_resources.getString(APP_SUCCESS));            namefield.setText(null);            addressfield.setText(null);            phonefield.setText("");            websitefield.setText("");            specialtyfield.setText("");            }        };               private MenuItem getItem = new MenuItem(_resources.getString(MENUITEM_GET), 110, 11) {            public void run() {               synchronized(store) {                   _data = (Vector)store.getContents();                   if (!_data.isEmpty()) {                      RestaurantInfo info = (RestaurantInfo)_data.lastElement();                      namefield.setText(info.getElement(RestaurantInfo.NAME));                      addressfield.setText(info.getElement(RestaurantInfo.ADDRESS));                      phonefield.setText(info.getElement(RestaurantInfo.PHONE));                      websitefield.setText(info.getElement(RestaurantInfo.WEBSITE));                      specialtyfield.setText(info.getElement(RestaurantInfo.SPECIALTY));                      }                   }               }            };                       private MenuItem phoneItem = new MenuItem(_resources.getString(MENUITEM_PHONE), 110, 12) {               public void run() {                   synchronized(store) {                      String phoneNumber = phonefield.getText();                      if ( phoneNumber.length() == 0) {                          Dialog.alert(_resources.getString(ALERT_NO_PHONENUMBER));                          }                      else {                          PhoneArguments call =                             new PhoneArguments(PhoneArguments.ARG_CALL, phoneNumber);                          Invoke.invokeApplication(Invoke.APP_TYPE_PHONE, call);                          }                      }                   }               };                             private MenuItem browserItem =                   new MenuItem(_resources.getString(MENUITEM_BROWSER), 110, 12) {                   public void run() {                      synchronized(store) {                          String websiteUrl = websitefield.getText();                          if (websiteUrl.length() == 0) {                             Dialog.alert(_resources.getString(ALERT_NO_WEBSITE));                             }                          else {                             BrowserSession visit = Browser.getDefaultSession();                             visit.displayPage(websiteUrl);                             }                          }                      }                   };                                     static {                      _resources = ResourceBundle.getBundle(                             RestaurantResource.BUNDLE_ID,                             RestaurantResource.BUNDLE_NAME);                      store = PersistentStore.getPersistentObject(0xdec6a67096f833cL);                                           // Key is hash of test.samples.restaurants.                      synchronized (store) {                          _data = (Vector)store.getContents();                          if (_data == null) {                             _data = new Vector();                             store.setContents( _data );                             store.commit();                             }                          }                      }                   public static void main(String[] args) {                      Restaurants app = new Restaurants();                      app.enterEventDispatcher();                      }                                     private static final class RestaurantInfo implements Persistable {                      // Data.                      private String[] _elements;                      // Fields.                      public static final int NAME = 0;                      public static final int ADDRESS = 1;                      public static final int PHONE = 2;                      public static final int WEBSITE = 3;                      public static final int SPECIALTY = 4;                      public RestaurantInfo() {                          _elements = new String[4];                          for ( int i = 0; i < _elements.length; ++i) {                             _elements[i] = new String("");                             }                          }                      public String getElement(int id) {                          return _elements[id];                          }                                           public void setElement(int id, String value) {                          _elements[id] = value;                          }                      }                                     public Restaurants() {                      MainScreen mainScreen = new MainScreen();                      mainScreen.setTitle(new LabelField(                             _resources.getString(APPLICATION_TITLE)));                      namefield = new AutoTextEditField(                             _resources.getString(FIELD_NAME), "");                      addressfield = new AutoTextEditField(                             _resources.getString(FIELD_ADDRESS), "");                      phonefield = new EditField(                             _resources.getString(FIELD_PHONE), "", Integer.MAX_VALUE,                             BasicEditField.FILTER_PHONE);                      websitefield = new EditField(                             _resources.getString(FIELD_WEBSITE), "",                             Integer.MAX_VALUE,BasicEditField.FILTER_URL);                      specialtyfield = new EditField(                             _resources.getString(FIELD_SPECIALTY), "",                             Integer.MAX_VALUE, BasicEditField.FILTER_DEFAULT);                      mainScreen.add(namefield);                      mainScreen.add(addressfield);                      mainScreen.add(phonefield);                      mainScreen.add(websitefield);                      mainScreen.add(specialtyfield);                      mainScreen.addKeyListener(this);                      mainScreen.addTrackwheelListener(this);                      pushScreen(mainScreen);                      }                                     public void makeMenu( Menu menu, int instance ) {                      menu.add(saveItem);                      menu.add(getItem);                      menu.add(phoneItem);                      menu.add(browserItem);                      super.makeMenu(menu, instance);                      }                                     public void onExit() {                      Dialog.alert(_resources.getString(APP_EXIT));                      }                   } Last Updated:2007年2月1日

posted on 2006-04-28 22:28 Confach 阅读(1731) 评论(7)  编辑 收藏 网摘 所属分类: BlackBerry

评论

#1楼 2007-01-30 21:37 赵建雄[未注册用户]

真的很强!   回复  引用    

#2楼 2008-07-23 11:36 babykite[未注册用户]

Question:
I defined one variable: PersistentObject store;
When referring to this object, it prompts one message:
1. Class requires signing with "RIM Runtime API"
2. Signing preferences may be changed using "Edit/Preferences/Code Signing"

What does it mean? Do I need to send request to RIM if I want to use this object?

But when I debug, I can save and get data from the object.
Why?
  回复  引用    

#3楼[楼主] 2008-07-23 11:45 Confach      

@babykite
Yes, it seems you are using the controlled API, you have to send a request to rim to get a code signature key , which will cost you 20 -100 US dollars , if you decide to run it on your real device.

You mentioned that you can bebug it, as I known so far, it can debug and run simulator, and can debug in real device, but not work if run in product environment.

Contact me if any question.
  回复  引用  查看    

#4楼 2008-07-23 11:54 babykite[未注册用户]

Thanks confach.
Does the persistent data store in the .cod file? Is there anything like database which is especially for data store? Hehe, still don’t have a concept about the data store. :p
  回复  引用    

#5楼[楼主] 2008-07-23 12:47 Confach      

@babykite
persistent data store, explain-self, is stored in the hardware, you can think it as the data is saved in our hard disk of our PC.
SO the data store is not saved in .cod file, since cod file just is an installment file, like the setup.exe for PC。

That is all.

thanks
  回复  引用  查看    

#6楼 2008-07-23 13:48 babykite[未注册用户]

@Confach
Thank you, Confach.
  回复  引用    

#7楼[楼主] 2008-07-23 14:45 Confach      

@babykite
you are welcome
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 387952




相关文章:

相关链接:

导航

公告


所有文章禁止转载,若想推荐或收藏,请用链接的形式。

哈罗,作者

Shared Readings


统计

与我联系

搜索

 

常用链接

留言簿

我参与的团队

我的标签

随笔分类(100)

随笔档案(95)

文章分类(79)

文章档案(82)

相册

Blogs

积分与排名

最新评论

阅读排行榜

评论排行榜

Blackberry Document