专注于blackberry » BlackBerry 应用程序开发者指南 第二卷:高级–第11章 管理通知(Notification)

      专注于Blackberry

BlackBerry 应用程序开发者指南 第二卷:高级–第11章 管理通知(Notification)

Taigoo 发表于 April 29, 2008 6:04 am

版权信息 :严禁转载, 若想推荐或收藏,请用链接的形式.

网址:http://www.36sign.com/bb/web/development/docs/guide-vol-2-manage-notification.html

11

           第11 管理通知(Notification)

通知API

增加事件

响应事件

自定义系统的通知

通知API

通知API(net.rim.device.api.notification)让你可以为你的应用程序增加自定义事件,并且当自定义事件发生时,又允许你定义用户接收的通知类型.

:当你的应用程序第一次访问通知API,它将检查一个ControlledAccessException.如果系统管理员使用应用程序控制限制访问通知API,一将抛出个运行时异常.为获取更多信息,参看BlackBerry应用程序开发者指南 2:高级 第一卷:基础”.

通知事件类型

描述

短暂事件

系统通知,例如LED闪烁,振动或铃声.

延续事件

应用程序指定的通知,例如一个UI.

 

对于短暂事件,只要事件一发生,BlackBerry设备就会使用一个系统通知, 例如LED闪烁,振动或铃声,尽快将消息发送给用户.应用程序不可以请求一指定类型的通知.在手持设备配置(Profile)列表里,用户通过选择一个活动的配置和设置配置选项来控制它们如何接收短暂事件的通知.为了将一个自定义的系统通知加入到短暂事件,请实现Consequence接口.

在延续的事件里,BlackBerry设备根据事件的优先级将它们安排在一个队列里.当事件发生时,事件影响的应用程序可能为用户提供一个自定义的通知,一般是通过显示一个用户界面(UI)元素,例如一个对话框来完成.为了监听延续的事件,实现NotificationsEngineListener接口.BlackBerry设备不会为延续事件提供一个系统级的通知.

增加事件

注册一个新的事件源

创建一个唯一long ID

为每个通知事件定义一个long ID.

public static final long ID_1 = 0xdc5bf2f81374095L;

:使用BlackBerry IDE将一个String转化为一个long,为你的应用程序创建一个long的标记符:

1.BlackBerry IDE文本里,输入一个字符串.
         2.
选择字符串,右击,然后单击Convert “string” to Long.

 

定义一个源对象

定义一个为事件提供源的对象. toString()的实现返回显示在配置列表里的字符串.

Object event = new Object() {

    public String toString() {

       return "Notification Demo";

       }

}

 

将你的应用程序注册一个通知源

为了将你的应用程序作为一个事件源加入到手持设备的配置列表里,调用NotificationsManager.registerSource().在此方法里,指定一个唯一的事件ID,源对象以及通知级别.

通知级别设置了事件的优先级,它决定了延续事件发生的顺序.以最高级到最低级的顺序,级别如下:

  • NotificationsConstants.CRITICAL
  • NotificationsConstants.SENSITIVE
  • NotificationsConstants.IMPORTANT
  • NotificationsConstants.DEFAULT_LEVEL
  •  NotificationsConstants.CASUAL

 :优先级仅适用于延续事件.只要短暂事件触发,它们就会发生.当触发一个延续事件时,指定一个过期时间.如果事件在最高级事件之前已经过期,用户可能不会接收到此最低级事件的通知.

BlackBerry设备启动时注册事件源

为了注册一个事件源,创建一个带有libMain()的工程,BlackBerry设备启动时进行注册.

 

创建一个类库工程

1.BlackBerry IDE, 创建一个工程.

2.右击工程,单击Properties.

3.单击 Application标签.

4.Project type 下拉列里,单击Library.

5.选择Auto-run on startup.

6.单击OK.

7.定义libMain().

public static final long ID_1 = 0xdc5bf2f81374095L;

public static final Object event = new Object() {

    public String toString()

    {

       return "Sample Notification Event #1";

       }

    };

    public static void libMain(String[] args) {

       NotificationsManager.registerSource(ID_1, event,

              NotificationsConstants.CASUAL);

}

触发一个短暂事件

调用triggerImmediateEvent().短暂事件由标准的系统通知描述,例如铃声,振动,LED.

NotificationsManager.triggerImmediateEvent(ID_1, 0, this, null);

triggerImmediateEvent方法接受下面的参数:

参数

描述

sourceID

启动事件(当调用registerSource()时指定)的应用程序的标志符.

eventID

应用程序事件标志符.

eventReference

应用程序事件cookie.

context

可选的上下文对象.

在大多数情况下,不要使用短暂事件,因为BlackBerry设备事件通知不会充分说明发生了什么事情.例如,如果BlackBerry设备振动,对于用户来说,它将很难知道到底是在你的应用程序里发生了一个事件,还是一个新消息已经到达了.如果你使用了短暂消息,考虑实现一个自定义的通知,例如一个特殊的铃声,来区分你的应用程序事件和其他BlackBerry设备事件.为获取更多信息,参看122页的自定义系统通知”.

触发一个延续事件

调用negotiateDeferredEvent().一个延续事件让你的应用程序以一个UI元素,例如一个对话框通知用户

NotificationsManager.negotiateDeferredEvent(ID_1, 0, this, -1,

NotificationsConstants.MANUAL_TRIGGER, null);

 

negotiateDeferredEvent(long, long, Object, long, int, Object) 方法接受下面的参数:

参数

描述

sourceID

启动事件(当调用registerSource()时指定)的应用程序的标志符.

eventID

应用程序事件标志符.

eventReference

应用程序事件cookie.

timeout

事件过期时间,以毫秒计,当调用方法时的相对时间(忽略过期时间,除非triggerOUT_OF_HOLSTER_TRIGGER)

trigger

要么是NotificationsConstants.OUT_OF_HOLSTER_TRIGGER,它指定了当BlackBerry设备和计算机断开时的事件,要么是NotificationsConstants.MANUAL_TRIGGER,它指定了应用程序本身触发事件.

context

可选对象,可以存储附加的,任意的参数来控制事件通知的状态或者行为.

 

如果你调用negotiateDeferredEvent(long, long, Object, long, int, Object),你的应用程序必须实现NotificationEventListener来监听事件并充分响应它.为获取更多信息,参看121页的响应事件”.

取消一个事件

取消一个短暂事件

调用cancelImmediateEvent(long, long, Object, Object), 然后指定源以及事件ID

 NotificationsManager.cancelImmediateEvent(ID_1, 0, this, null);

 

取消一个延续事件

调用 cancelDeferredEvent(long, long, Object, int, Object), 然后指定源以及事件ID

NotificationsManager.cancelDeferredEvent(ID_1, 0, this, NotificationsConstants.MANUAL_TRIGGER, null);

 

取消所有延续事件

调用cancelAllDeferredEvents(long, int, Object)方法取消所有应用程序启动的是延续事件.

NotificationsManager.cancelAllDeferredEvents(ID_1, NotificationsConstants.MANUAL_TRIGGER, null);

 .如果你调用negotiateDeferredEvent()方法,但没有指定过期事件,你必须调用cancelDeferredEvent()方法取消事件,否则事件永远不过期.

 

代码实例


: NotificationDemo.java

/**

* NotificationsDemo.java

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

*/

package com.rim.samples.docs.notifications;

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

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 com.rim.samples.docs.baseapp.*;

 

public class NotificationsDemo extends BaseApp {

    public static final long ID_1 = 0xdc5bf2f81374095L;

    private long _eventIdGenerator;

    private static Object er;

    public static final Object event = new Object() {

       public String toString() {

           return "Sample Notification Event #1";

           }

       };

      

       public static void main(String[] args) {

           NotificationsManager.registerSource(ID_1, event, NotificationsConstants.CASUAL);

           NotificationsManager.registerConsequence(ConsequenceDemo.ID,

                  new ConsequenceDemo());

           NotificationsDemo app = new NotificationsDemo();

           app.enterEventDispatcher();

           }

      

       public NotificationsDemo() {

           MainScreen mainScreen = new MainScreen();

           mainScreen.setTitle("Notification Demo App");

           mainScreen.addKeyListener(this);

           mainScreen.addTrackwheelListener(this);

           NotificationsManager.registerNotificationsEngineListener(ID_1,

                  new NotificationsEngineListenerImpl(this));

           pushScreen(mainScreen);

           }

      

       private MenuItem triggerItem = new MenuItem(null, 0, 100, 10) {

           public void run() {

              NotificationsManager.triggerImmediateEvent(ID_1, 0, this, null);

              }

           public String toString() {

              return "Trigger event";

              }

           }

       };

      

       private MenuItem deferItem = new MenuItem(null, 0, 100, 10) {

           public void run() {

              long timeout = -1; // Ignored unless trigger is OUT_OF_HOLSTER_TRIGGER.

              int trigger = NotificationsConstants.MANUAL_TRIGGER;

              Object er = new Object();

              NotificationsManager.negotiateDeferredEvent(ID_1, ++_eventIdGenerator,

                     er, timeout, trigger, null);

              }

          

           public String toString() {

              return "Start deferred event";

              }

           };

          

           private MenuItem cancelItem = new MenuItem(null, 0, 100, 10) {

              public void run() {

                  int trigger = NotificationsConstants.MANUAL_TRIGGER;

                  NotificationsManager.cancelDeferredEvent(ID_1, _eventIdGenerator, er,

                         trigger, null);

                  }

              public String toString() {

                  return "Cancel deferred event";

                  }

              };

             

              public void makeMenu( Menu menu, int instance ) {

                  menu.add(triggerItem);

                  menu.add(deferItem);

                  menu.add(cancelItem);

                  super.makeMenu(menu, instance);

                  }

             

              public void onExit() {

                  System.exit(0);

                  }

             

              private static class NotificationsEngineListenerImpl implements

                         NotificationsEngineListener {

                  private UiApplication _app;

                  }

             

              public NotificationsEngineListenerImpl(UiApplication app) {

                  _app = app;

                  }

             

              public void deferredEventWasSuperseded(long sourceID, long eventID,

                     Object eventReference, Object context) {

                  final long _eventID = eventID;

                  er = eventReference;

                  _app.invokeLater(new Runnable() {

                     public void run() {

                         NotificationsManager.cancelDeferredEvent(ID_1, _eventID, er,

                                NotificationsConstants.MANUAL_TRIGGER, null);

                         }

                     });

                  }

             

              public void notificationsEngineStateChanged(int stateInt, long sourceID,

                     long eventID, Object eventReference, Object context) {

                  if(stateInt == NotificationsConstants.OUT_OF_HOLSTER_ENGINE_STATE) {

                     // Perform some action if handheld is removed from holster.

                     }

                  if(stateInt == NotificationsConstants.IN_HOLSTER_ENGINE_STATE) {

                     // Perform some action if handheld is inserted into holster.

                     }

                  }

             

              public void proceedWithDeferredEvent(long sourceID, long eventID,

                     Object eventReference, Object context) {

                  final long _eventID = eventID;

                  }

              _app.invokeLater(new Runnable() {

                  public void run() {

                     String s = "This event has occurred: " + _eventID;

                     Dialog d = new Dialog(Dialog.D_OK, s, Dialog.OK,

                            Bitmap.getPredefinedBitmap(Bitmap.INFORMATION), 0);

                      };

                    

                     private MenuItem cancelItem = new MenuItem(null, 0, 100, 10) {

                         public void run() {

                            int trigger = NotificationsConstants.MANUAL_TRIGGER;

                            NotificationsManager.cancelDeferredEvent(ID_1,

                                   _eventIdGenerator, er,trigger, null);

                            }

                         public String toString() {

                            return "Cancel deferred event";

                            }

                         };

                        

                         public void makeMenu( Menu menu, int instance ) {

                            menu.add(triggerItem);

                            menu.add(deferItem);

                            menu.add(cancelItem);

                            super.makeMenu(menu, instance);

                            }

                        

                         public void onExit() {

                            System.exit(0);

                            }

                        

                         private static class NotificationsEngineListenerImpl

                         implements NotificationsEngineListener {

                            private UiApplication _app;

                            public NotificationsEngineListenerImpl(UiApplication app) {

                                _app = app;

                                }

                           

                            public void deferredEventWasSuperseded(long sourceID,

                                   long eventID,Object eventReference, Object context)

                            {

                                final long _eventID = eventID;

                                er = eventReference;

                                _app.invokeLater(new Runnable() {

                                   public void run() {

                                       NotificationsManager.cancelDeferredEvent(ID_1,

                                              _eventID, er,

                                              NotificationsConstants.MANUAL_TRIGGER, null);

                                       }

                                   });

                                }

                            public void notificationsEngineStateChanged(int stateInt, long sourceID,

long eventID, Object eventReference, Object context) {

                                if(stateInt == NotificationsConstants.OUT_OF_HOLSTER_ENGINE_STATE) {

                                   // Perform some action if handheld is removed from holster.

                                   }

                                if(stateInt == NotificationsConstants.IN_HOLSTER_ENGINE_STATE) {

                                   // Perform some action if handheld is inserted into holster.

                                   }

                                }

                           

                            public void proceedWithDeferredEvent(long sourceID, long eventID,

                                   Object eventReference, Object context) {

                                final long _eventID = eventID;

                                _app.invokeLater(new Runnable() {

                                   public void run() {

                                       String s = "This event has occurred: " + _eventID;

                                       Dialog d = new Dialog(Dialog.D_OK, s, Dialog.OK,

                                              Bitmap.getPredefinedBitmap(Bitmap.INFORMATION), 0);

                                       d.show();

                                       }

                                   });

                                }

                            }

                         }

响应事件

NotificationsEngineListener的实现定义了自定义的通知.调用negotiateDeferredEvent()注册你的实现.如果你触发一个短暂事件,你没有必要为每个BlackBerry设备提供的标准系统通知实现监听者.

为延续事件提供一个自定义的UI通知

NotificationsEngineListener接口的实现为延续事件提供了一个自定义的UI通知.为获取关于创建UI的更多信息,参看BlackBerry应用程序开发者指南 2:高级 第一卷基础.

private static class ListenerImpl implements NotificationsEngineListener

{…}

 

定义挂起事件的行为

deferredEventWasSuperseded()的实现定义了当一个延续事件挂起时发生的事情.当一个事件由另外一个相同的或者优先级更高的事件挂起时,调用此方法.例如,如果事件挂起时,你可以取消此事件.

public void deferredEventWasSuperseded(long sourceID, long eventID,

       Object eventReference, Object context) {

    final long _eventID = eventID;

    er = eventReference;

    _app.invokeLater(new Runnable() {

       public void run() {

           NotificationsManager.cancelDeferredEvent(ID_1, _eventID, er,

                  NotificationsConstants.MANUAL_TRIGGER, null);

           }

       });

}

 

定义套座(holstering)行为

notificationsEngineStateChanged()的实现定义了套座的行为.BlackBerry设备插入到套座或从套座移出时,调用此方法.例如,当安排了一个延续事件,并且BlackBerry设备和计算机已经相连或断开时,你可以完成一个指定的操作.

public void notificationsEngineStateChanged(int stateInt, long sourceID, long eventID, Object eventReference, Object context) {

if(stateInt == otificationsConstants.OUT_OF_HOLSTER_ENGINE_STATE) {

    // Perform action if BlackBerry device is removed from holster.

}

if(stateInt == NotificationsConstants.IN_HOLSTER_ENGINE_STATE) {

    // Perform action if BlackBerry device is inserted into holster.

    }

}

 

定义通知

proceedWithDeferredEvent()的实现定义了当发生事件时如何通知用户,例如显示一个对话框.当监听者处理一个事件(没有其他更高优先级的事件在队列里),调用此方法.

public void proceedWithDeferredEvent(long sourceID, long eventID, Object eventReference,Object context) {

    final long _eventID = eventID;

    _app.invokeLater(new Runnable() {

       public void run() {

           String s = "This event has occurred: " + _eventID;

           Dialog d = new Dialog(Dialog.D_OK, s, Dialog.OK,

                  Bitmap.getPredefinedBitmap(Bitmap.INFORMATION), 0);

           d.show();

           _eventHashtable.put(_eventID, d);

           }

       });

      }

注册通知监听者

调用registerNotificationsEngineListener(int, NotificationsEngineListener)NotificationsManager注册监听者.提供应用程序的事件源ID以及实现NotificationsEngineListener接口的类的实例作为参数.

NotificationsManager.registerNotificationsEngineListener( ID_1, new ListenerImpl(this));

:每个应用程序只能注册一个NotificationsEngineListener

自定义系统通知

Consequence接口的实现为短暂事件创建了一个系统通知,例如特殊的铃声,或当发生事件,例如创建一个日志记录接收到的通知数时,进行其他的操作.

:Consequence接口仅使用在需要系统通知的短暂事件上.延续事件需要你的应用程序实现NotificationsEngineListener接口,并且以一个特定的应用程序回应作为响应.

在主屏幕上提供一个应用程序,使用户可以设置通知选项.

响应一个通知事件

Consequence SyncConverter接口的实现对通知事件做出响应. Consequence接口定义了一个对通知事件做出的应用程序响应.SyncConverter接口定义了需要的功能将一个对象转化为序列化格式.这需要使BlackBerry设备可以备份和恢复Profile配置.为获得更多信息,参看90页的”BlackBerry持久存储”.

private static class ConsequenceImpl implements Consequence,SyncConverter {…}

定义一个唯一ID

为结果(consequence)定义一个唯一ID.

public static final long ID = 0xbd2350c0dfda2a51L;

定义常量

为应用程序定义DATATYPE常量.convert()调用时,为了标记从SyncConverter进来的数据类型,使用这些常量.对于适合的应用程序的数据来说,它们是任意的标志符.

private static final int TYPE = ‘n’ << 24 | ‘o’ << 16 | ‘t’ << 8 | ‘d’;

private static final byte[] DATA = new byte[] {‘m’, ‘y’, ‘-’, ‘c’,

‘o’, ‘n’, ‘f’, ‘i’, ‘g’, ‘-’, ‘o’, ‘b’, ‘j’, ‘e’, ‘c’, ‘t’};

private static final Configuration CONFIG = new Configuration(DATA);

创建一个铃声

作为事件通知的一部分,创建一个铃声来播放.

private static final short BFlat = 466; // 466.16

private static final short TEMPO = 125;

private static final short d16 = 1 * TEMPO;

private static final short dpause = 10; // 10 millisecond pause

private static final short[] TUNE = new short[] {BFlat, d16, pause, BFlat};

private static final int VOLUME = 80; // Percentage volume.

定义一个通知

startNotification()的实现为本结果定义了通知.下面的代码实例里,LED将会闪烁,并且会播放一个铃声.

public void startNotification(long consequenceID, long sourceID, long eventID, Object configuration, Object context) {

    LED.setConfiguration(500, 250, LED.BRIGHTNESS_50);

    LED.setState(LED.STATE_BLINKING);

    Alert.startAudio(TUNE, VOLUME);

    Alert.startBuzzer(TUNE, VOLUME);

}

停止一个通知

stopNotification()的实现停止本结果的通知.

public void stopNotification(long consequenceID, long sourceID,

       long eventID, Object configuration, Object context) {

    LED.setState(LED.STATE_OFF);

    Alert.stopAudio();

    Alert.stopBuzzer();

    }

设置用户Profile选项

newConfiguration()的实现创建一个新的配置对象来存储用户资料的设置.此对象传到结果的实现中,以决定用户指定的结果类型是否合适事件.下面的代码实例返回早期定义的CONFIG对象

public Object newConfiguration(long consequenceID, long sourceID,

       byte profileIndex, int level, Object context) {

    return CONFIG;

    }

启动BlackBerry设备数据备份

SyncConverter.convert()的实现可以备份BlackBerry设备数据.当把BlackBerry设备上的数据备份到用户计算机上时,调用此方法.下面的实例从DatBuffer读取进入的数据,并且对未经处理的数据应用一个4个字节的类型和长度.

public SyncObject convert(DataBuffer data, int version, int UID) {

    try {

       int type = data.readInt();

       int length = data.readCompressedInt();

       if ( type == TYPE ) {

           byte[] rawdata = new byte[length];

           data.readFully(rawdata);

           return new Configuration(rawdata);

           }

       }

    catch (EOFException e) {

       System.err.println(e);

       }

    return null;

    }

启动BlackBerry设备数据恢复

SyncConverter.convert()的实现恢复BlackBerry设备上的数据.当数据从用户计算机上恢复到BlackBerry设备上时,调用此方法.

public boolean convert(SyncObject object, DataBuffer buffer, int version) {

    boolean retval = false;

    if ( object instanceof Configuration ) {

       Configuration c = (Configuration)object;

       buffer.writeInt(TYPE);

       buffer.writeCompressedInt(c._data.length);

       buffer.write(c._data);

       retval = true;

       }

    return retval;

}

定义通知配置

创建一个类描述通知配置信息.此类实现了SyncObjectPersistable.你必须实现SyncObject.getUID()方法,但是如果数据同步不需要,你的实现可以返回0,正如下面的例子.

private static final class Configuration implements SyncObject, Persistable {

    public byte[] _data;

    public Configuration(byte[] data) {

       _data = data;

       }

    public int getUID() {

       return 0;

       }

}

注册一个结果

如果你创建一个自定义的Consequence,调用registerNotificationsObjects(long, Consequence)方法将之在NotificationsManager上注册.

NotificationsManager.registerConsequence(ConsequenceImpl.ID, new ConsequenceImpl());

BlackBerry设备启动时,为了注册consequence,在一个类库工程里完成这个注册.为获得更多信息,参看116页的BlackBerry启动时注册一个事件源”.

代码实例


: ConsequenceDemo.java

/**

* ConsequenceDemo.java

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

*/

package com.rim.samples.docs.notifications;

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

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

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

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

import java.io.*;

 

public class ConsequenceDemo implements Consequence, SyncConverter {

    public static final long ID = 0xbd2350c0dfda2a51L;

    private static final int TYPE = ‘n’ << 24 | ‘o‘ << 16 | ‘t‘ << 8 | ‘d‘;

    private static final byte[] DATA = new byte[] {

       ‘m’, ‘y’, ‘-’, ‘c’, ‘o‘, ‘n‘, ‘f‘, ‘i’,

       ‘g’, ‘-‘, ‘o‘, ‘b‘, ‘j‘, ‘e‘, ‘c‘, ‘t‘ };

    private static final Configuration CONFIG = new Configuration(DATA);

    private static final short BFlat = 466; // The actual value is 466.16.

    private static final short TEMPO = 125;

    private static final short d16 = 1 * TEMPO;

    private static final short pause = 10; // 10 millisecond pause.

    private static final short[] TUNE = new short[] {BFlat, d16, pause, BFlat};

    private static final int VOLUME = 80; // Percentage volume.

   

    public void startNotification(long consequenceID, long sourceID, long eventID,

           Object configuration, Object context) {

           LED.setConfiguration(500, 250, LED.BRIGHTNESS_50);

           LED.setState(LED.STATE_BLINKING);

           Alert.startAudio(TUNE, VOLUME);

           Alert.startBuzzer(TUNE, VOLUME);

           }

      

    public void stopNotification(long consequenceID, long sourceID, long eventID,

              Object configuration, Object context) {

           LED.setState(LED.STATE_OFF);

           Alert.stopAudio();

           Alert.stopBuzzer();

           }

      

    public Object newConfiguration(long consequenceID, long sourceID,

              byte profileIndex, int level, Object context) {

           return CONFIG;

           }

   

    public SyncObject convert(DataBuffer data, int version, int UID) {

       try {

           int type = data.readInt();

           int length = data.readCompressedInt();

           if ( type == TYPE ) {

              byte[] rawdata = new byte[length];

              data.readFully(rawdata);

              return new Configuration(rawdata);

              }

           }

       catch (EOFException e) {

           System.err.println(e);

           }

    return null;

    }

   

    public boolean convert(SyncObject object, DataBuffer buffer, int version) {

       boolean retval = false;

       if ( object instanceof Configuration ) {

           Configuration c = (Configuration)object;

           buffer.writeInt(TYPE);

           buffer.writeCompressedInt(c._data.length);

           buffer.write(c._data);

           retval = true;

           }

       return retval;

       }

   

    /* Inner class to store configuration profile. */

    private static final class Configuration implements SyncObject, Persistable {

       public byte[] _data;

       public Configuration(byte[] data) {

           _data = data;

           }

       public int getUID() {

           return 0;

           }

       }

    }


Last  Updated:2007年2月5日

相关文章:

  1. BlackBerry 应用程序开发者指南 第一卷:基础–第11章 使用位置信息
  2. BlackBerry 应用程序开发者指南 第二卷:高级–第4章 增加设备选项
  3. BlackBerry 应用程序开发者指南 第二卷:高级–第5章 BlackBerry浏览器
  4. BlackBerry 应用程序开发者指南 第一卷:基础–第7章 使用数据报(Datagram)连接
  5. BlackBerry 应用程序开发者指南 第二卷:高级–第9章 备份和恢复持久数据



发表评论