November 24th, 2009progress on login

这里是指在linux下, 在我们的环境中由于在登录系统的时候需要比较久的时候(5-15秒), 在那里看着不动的屏幕总归会有些不爽, 于是便写了一个小小的进度条来.
首先呢, 我们先完成一个画一个转圈的直线的程序来(其实就是轮流打印”-\|-/”这几个字符, 它每转完一圈后会打印一个加号.) — 当然每次打印要有一个间隔, 不然会很快, 这个时间间隔我们能过程序参数来传递.

#! /bin/bash
count=0
#c is the array for the characters we'll print
c[0]='-\b'
c[1]='\\\b'
c[2]='|\b'
c[3]='/\b'
c[4]='-\b'
c[5]='+'

while true
do
     let "count = $count %6"
     echo -n -e "${c[$count]}"
     sleep $1  #sleep sometime to continue....
     let "count = $count + 1"
done

在上面我们使用了echo 的两个参数, -n 是让它不打印一个多余的换行, -e让它解释转义字符, 而\b则是退格符.
好的, 下面就保存这个脚本并让它可执行(chmod +x ), 这里我把它命名为~/bin/timedot, 能过执行 ./timedot 0.2 来看一下效果吧. (请确定你的sleep程序可以接受小数形式的参数).

嗯, 看上去不错, 好的, 让我们把它加入到启动脚本里, 基本思路是把它作为一个后台程序运行.

~/bin/timedot 0.2&
timedotPID=$! #$! 是bash的内置变量, 它保存最后一个后台进行的pid.
...... #把你的很慢的代码放在这里.

kill $timedotPID  #杀掉打印进度条的进程.

好了, 试一下, 嗯, 似乎工作的还不错. 不过似乎有一个问题: 在每次启动的时候你都会看到一个类似下面的消息:

[1234]-  Terminated                 ~/bin/timedot 0.2&

之所以会有这行消息是因为你在后台运行一个程序的时候, bash会关注所有属于它的后台进程的状态, 并在后台进程结束后通知前台程序某个程序已经死掉啦(或者是已经完成了), 不过毫无疑问我们不希望看到关于timedot的通知消息(太丑陋了), 于是我们可以通过disown 命令来让bash放弃对timedot的管理.

好, 在这里似乎一切都很完美了, 不过假如你在一次登录的时候, 实在忍受不了长时间的等待了, 于是你按下了ctrl+c来中止当前的初始化脚本, 然后你就会发现了另外一个问题… 你会发现在你打字的时候总是会有”|/|-+”这几个字符蹦出来 —- 而这是因为你的ctrl+c虽然中止了你的初始化脚本, 但它却没有中此我们的这个后台进行的进度条进程. 好吧, 让我们修好它:

~/bin/timedot 0.2&
timedotPID=$! #$! 是bash的内置变量, 它保存最后一个后台进行的pid.
my_control_c()
{
     kill $timedotPID #杀死后台进程<br />
     trap SIGINT  #重置SIGINT,
}
trap my_control_c SIGINT   #我们设置一个control+c的钩子, 当用户按下control+c的时候会调用我们的my_control_c函数
...... #把你的很慢的代码放在这里.

kill $timedotPID  #杀掉打印进度条的进程.
trap SIGINT #重置SIGINT为默认值.

这里使用了trap命令来设置ctrl+c的钩子, 这样每次要中止初始化脚本的时候就会调用我们自己的函数, 而它会杀掉后台进度条进程, 当然不要忘记重置钩子, 我们不需要再以后用到它了.

刚搞了个黑莓玩, 在写短信时发现缺少了一个在用wm时常用的功能: 往短信中插入联系人. 这个有啥用呢, 想一想当有朋友问题你要另外一个人的号码时, 你就会知道它的用处了, 简单的通过一个菜单就可以插入联系人的手机号, 邮箱…… 而在黑莓里是没有这个功能的, 只能先切换到桌面然后打开联系人界面然后再找到你要的联系人, 然后再把电话复制出来, 再然后就是切换到短信程序并粘贴电话号码, 这显然是一个比较烦琐的步骤. 再假如你想要同时插入邮件/手机号, 则还要来回复制两遍. 所以我们就会看到一些常用的扩展来辅助插入手机号. 于是闲来无事自己研究了一下具体实现 (已经看到了一些具体的实现, 只是好奇, 所以研究了一下)

让我们分析一下这个问题, 要实现这个功能, 应该可以把功能划分为以下三个步骤:

  1. 往编辑短信的菜单中添加一个菜单项
  2. 当用户点击这个菜单的时候显示一个选择联系人的界面
  3. 当用户选择联系人之后把联系人的电话插入到短信中去.

下面我们分别看这三个步骤的实现, 步骤一很简单, BB提供了相关的api, 在它的文档中就可以找到, 具体请看这里. 首先我们需要实现一个派生自MenuItem类,

class InsertContactMenuItem extends ApplicationMenuItem {
   InsertContactMenuItem() {
                     super(2000000);  // This sets the item just before the "Add To:" menuitem          }

   public String toString() {             return "Insert Contact";
   }

  public Object run(Object context) {              ....

  }

上面就是一个简单的程序菜单项, 它在初始化的时候通过调用父类的构造函数来调整它在菜单项中的顺序(这个没有试过, 具体不知道工作不工作…), toString方法返回的字符串决定了它显示在菜单中的文字, 至于run方法则会在点击菜单项时会调用到, 它接受一个context参数, 系统会传入一个上下文相关的对像.

有了Menu类之后就可以把它注册到短信编辑界面里去了:

   private static void registerMenuItem() {
        System.out.println("*** Registering Insert contact menuitem");
        InsertContactMenuItem menuItem = new InsertContactMenuItem();
        ApplicationMenuItemRepository amir = ApplicationMenuItemRepository.getInstance();
        amir.addMenuItem(ApplicationMenuItemRepository.MENUITEM_SMS_EDIT, menuItem);
        }

上面的逻辑很简单, 首先取得系统ApplicationMenuItemRepository的实例, 并向注册InsertContactMenuItem到SMS_EDIT中去, 好了, 到了这里, 第一步基本完成了. 完成这一步, 你就可以在模拟器里看到相应的菜单项了, 如下图:

menu

下面开始第二步, 这里需要在用户点击菜单之后弹出一个选择联系人的界面, 这个可以通过BlackBerryContactList.choose()方法实现:

  PIM pim = PIM.getInstance();
  BlackBerryContactList list = (BlackBerryContactList) pim.openPIMList(PIM.CONTACT_LIST, PIM.READ_ONLY);
  PIMItem pimItem = list.choose ();
  String phoneNum = pimItem.getString  (Contact.TEL, 0);

这里首取得系统PIM的实例, 然后联系人列表并调用它的choose ()方法, 运行的结果应该如下图所示, 出现一个选择联系人的窗口:

choosecontact 到了这里, 最后一步就是要把联系人信息插入到短信输入窗口里了, 在这里我走了一段弯路:

在前面提到在系统调用run (Object context)方法时会传入一个上下文相关的对象, 在看4.5.0os的文档里提到这里会传入一个TextMessage对象, 具体参见这里, 当时我是想尝试直接通过修改这个对象来往消息里插入文本, 不过可惜的是行不能: 因为在这里传入的TextMessage对象是只读的, 尝试调用它的set方法会引发一个异常. 而且在老版本的系统里根本就没有传入一个对象(8800 4.2.0模拟器里显示传入的对象为空). 所以在经历了若干次失败后只好转求它法.

在阅读相关的代码的时候, 终于发现了一个UiApplication类, 通过它可以得到当前在最前面的屏幕, 并且我们可以遍历这个屏幕上的所有元素, 貌似有点希望! 让我们尝试遍历一当前屏幕并打印出这些元素, 看看它们有些什么!

  Screen screen = UiApplication.getUiApplication().getActiveScreen();
  for (int i = 0; i < screen.getFieldCount(); i++)
   {
        Field field = screen.getField(i);
        printFields (field);
   }
      void printFields (Field field)
         {
             try
             {
                    System.out.println("*** Field class: " + field.getClass().getName());

                         if (field instanceof  TextField) {
                             TextField textField = (TextField) field;
                             System.out.println("*** Found a text field");
                             System.out.println("*** text field label: "
                                     + textField.getLabel());

                                 System.out.println("*** Found text field");
                                 System.out.println ("** value: " + textField.getText ());
                         }
                         else if (field instanceof  Manager)
                         {
                             Manager mgrField = ( Manager) field;
                               System.out.println ("** Subfields..");
                                for (int i = 0; i < mgrField.getFieldCount(); i++) {
                                        Field subFields = mgrField.getField(i);
                                        printFields (subFields);
                        }
                    }
                }
                catch (Exception e)
                {
                    System.out.println ("printFields excetion:" + e.getMessage ());
                }
        }

对于这段代码, 前几行是得到当前活动的屏幕,  然后调用函数printFields函数, 在这个函数里我们基本是把一个field相关信息打印了出来: 它的标签的文字, 它的文字. 由于在screen里可以嵌套包含一些屏幕元素的管理对象, 所以我们需要一个递归来打印. 下面是得到的结果, 请注意使用蓝颜色标记的文字,

BB 8800 4.2.1.74的Debug输出

JVM: bklt @8044: timer

JVM: bklt @8044: idle 0

JVM: bklt @8044: setTimer 22

*** Field class: net.rim.device.api.ui.container.VerticalFieldManager

** Subfields..

*** Field class: net.rim.device.apps.internal.phone.model.ReadOnlyPhoneNumberField

** Subfields..

*** Field class: net.rim.device.apps.internal.phone.model.SmartPhoneNumberLabelField

*** Field class: net.rim.device.api.ui.component.SeparatorField

*** Field class: net.rim.device.apps.internal.sms.ui.SMSEditField

*** Found a text field

*** text field label: null

*** Found Using field

** value: Afdsf

BB  9500 的Debug输出:

FRIDG: could not find background_popup__Landscape.png

*** Field class: net.rim.device.api.ui.container.VerticalFieldManager

** Subfields..

*** Field class: net.rim.device.apps.api.ui.LeftRightFieldManager

** Subfields..

*** Field class: net.rim.device.api.ui.container.HorizontalFieldManager

** Subfields..

*** Field class: net.rim.device.api.ui.component.LabelField

*** Field class: net.rim.device.apps.internal.sms.ui.MessageComposeComboField

** Subfields..

*** Field class: net.rim.device.apps.api.addressbook.AddressBookComboField$EmailComposeEditable

*** Found a text field

*** text field label: To:

*** Found text field

** value: 12313

*** Field class: net.rim.device.apps.api.ui.LeftRightFieldManager

** Subfields..

*** Field class: net.rim.device.api.ui.container.HorizontalFieldManager

** Subfields..

*** Field class: net.rim.device.api.ui.component.LabelField

*** Field class: net.rim.device.apps.internal.sms.ui.MessageComposeComboField

** Subfields..

*** Field class: net.rim.device.apps.api.addressbook.AddressBookComboField$EmailComposeEditable

*** Found a text field

*** text field label: To:

*** Found text field

** value:

*** Field class: net.rim.device.apps.internal.smscompose.message.SMSEditorScreen$BodyVerticalFieldManager

** Subfields..

*** Field class: net.rim.device.apps.internal.smscompose.ui.SMSEditField

*** Found a text field

*** text field label: null

*** Found text field

** value: Wet

我们可以看到实际在这两个系统里的短信编辑输入框实际都是一个net.rim.device.apps.internal.smscompose.ui.SMSEditField 类, 看上去似乎我们可以通过判断一个Field对象是不是一个此类的实例来找到短信的实际输入框了, 十分遗憾的是请注意前面的那一连串类名里的一个.internal.: 对不起, 这是一个内部类, 外界开发人员是不能使用这个类的.不过我们还是可以利用这一点的, 在这里直接判断它的类名里是不是包含SMSEditField就可以达到预定效果.

      Screen screen = UiApplication.getUiApplication().getActiveScreen();
                    for (int i = 0; i < screen.getFieldCount(); i++)
                    {
                        Field field = screen.getField(i);
                        printFields (field, phoneNum);
                    }

      void printFields (Field field, String str)
         {
             try
             {
                           System.out.println("*** Field class: "
                                 + field.getClass().getName());

                         if (field instanceof  TextField) {
                             TextField textField = (TextField) field;
                             System.out.println("*** Found a text field");
                             System.out.println("*** text field label: "
                                     + textField.getLabel());

                                 System.out.println("*** Found text field");
                                 System.out.println ("** value: " + textField.getText ());
                                 if (textField.getClass ().getName ().endsWith(".SMSEditField")
                                 {
                                     System.out.println ("insertting text to...");
                                    textField.insert (str);
                                }

                         }
                         else if (field instanceof  Manager)
                         {
                             Manager mgrField = ( Manager) field;
                               System.out.println ("** Subfields..");
                                for (int i = 0; i < mgrField.getFieldCount(); i++) {
                                        Field subFields = mgrField.getField(i);
                                        printFields (subFields, str);
                        }
                    }
                }
                catch (Exception e)
                {
                    System.out.println ("printFields excetion:" + e.getMessage ());
                }
        }
        }

修改完代码后, 编译, 运行, 启动程序, 然后打开短信输入窗口, 按照前面的步骤选择联系人, 确认! 电话被成功的插入到短信编辑窗口里了!:)

resultsms

很好, 这里我们基本大功告成了! 不过其实还有很事情要做: 怎么把程序弄成开机启动的, 然后应该在选择联系人之后我们应该弹出一个对话框让用户选择需要插入的项(移动电话, 家庭电话, email 等). 不过做为一个自己瞎折腾型的项目, 到这里咱们就告一段落了:)

最后再告诉你一个不幸的消息, 即便你有了这个程序你也不能把它安装到你的手机里的, 这是因为黑莓开发的api中有一些是需要官方的验证的, 而这个验证在当前是需要钱滴(20刀). 不过即使没有申请官方的验证也是可以在模拟器里支持你的程序的, 这就是我写这篇文章的环境:)

具体的代码在这里:InsertContact.java

June 9th, 2009IE的怨念

– 因为写网页发现很多不兼容的郁闷的地方……

1. 数组声明

var a = {a: 1, b:2, c:3, }

注意最后一个”,”, 这样写在ie里是会报语法错误的……

2. 上面的例子, 如果没有初始化列表的最后一个逗号在ie中是可以的, 但是下面的呢?

var params = {class : “main”, a : 1};

在网页里写下这样的代码, 然后拿到ie里运行, 貌似没看出啥问题(我用的IE7), 可是你会发现你的代码为啥总没执行呢? 于是一行一行的删除代码, 最后n长时间过后总算发现是这行代码的问题, 可是为啥呢? 仔细看看, 貌似这里特别的地方就是在初始化列表里的class是一个关键字 — 是的, 就是它了, 对于有这种关键字的初始化列表是要加上双引号的, var paramrs = {“class” : “main”,  a : 1};, 这个问题很恼人 — 你支持{a : 1};为啥就不支持{class : 1};总感觉这里的实现像是半个残废.

3. css兼容性的问题, 太多了…… 导致看到好好的东西到了ie下就是不中啊, display:table-cell 在ie8之前的版本里不支持, 我哭.

4. 再另外像inject <tr> 到<table>里不工作, 必须得先把<tr> inject<tbody>里才行, 用的是mootools, 但估计也是IE的问题导致的, 反正是有了怨念, 就把这个也算到它的头上吧.

用javascript写程序, 想不到使用的感觉不错, 对js萌生出不少好感来, 有了好感后便是想着能在所有的地方用它, 于是便yy能在写本地程序的时候也用上js — 如果写本地程序可以用像写网页的方式写下来, 那该多完美哪 (当时yy的是一个程序可以以native/网页的形式运行的东西~~~). 记得当时找了一些, 貌似没有太多收获, 今天偶然又google了一把, 居然找到了一篇有趣的blog介绍gtk Seed, 果然有点意思.

Seed是个什么东西, 它的主页上有了介绍, 它是一个库, 解释器以及可以在运行时和webkit javascript核心引擎协作的东西.一句话它提供了你创建本地javascript程序的能力. 这个项目比较新的, 2008年11月8号才发布第一个版本. 这里有篇blog给了一些用它实现的小程序, 看上去还不错, 它封装的本地gtk api应该基本够用了, 没有仔细研究, 但至少文件/窗口消息啥的都有了. 另外貌似这里还用到了Gobject Introspection, 貌似很牛X的, 这个东西的目标之一应该是使用系统语言(像C之流)写的Gtk对象的窗口对象可以不经过任何额外的封装操作都都可以在其他语言中使用(像python/javascript) — 这样还是比较方便的. seed主页可以找到很多例子程序, 看了几个, 貌似很方便. 抽时间了搞下来折腾一下, 不知道有没有提供windows下的版本…

另外与Seed类似的还有另外一个项目:GJS, 这两个引擎的目标都很相似, 貌似都是为了提供本地化的开发能力, 区别就是Seed采用了webkit的js 核心, 而GJS则是使用的mozilla firefox的js引擎(Spidermonkey ). 使用spidermokey引擎的好处之一据说就是可以使用很多webkit中尚不支持的javascript5中引入的新语言特性. 不过看了一些文章, 似乎Seed和GJS的老大们还有过一些讨论目的是使得这两个引擎提供良好的兼容性, 其中Seed的老大似乎还同意修改Seed中Enum/import的实现以保持和GJS的一致.  不过相比较而言似乎Seed项目相对比较活跃一些, 更新比较频繁, 今年已经发而了五六个版本. 而GJS则相对比较沉闷了一些了, 似乎这半年没啥动静, 有点怀疑是不是要黄了……

另外既然讲到本地化的javascript开发, 就不得不提到adobe 的AIR了, 这个应该是和我原来yy的比较相似, 它支持html+js的本地程序, 看了一些文档, 提供了一些本地化的api(文件啊, 剪贴析啊之类的), 不过相对于前两者来讲应该是对于开发的限制应该多了不少. 但是这个酷在确实做到了web+native的双向执行啊, 另外就是它的开发工具相对全一些, 但是要收费滴.再另外就是Air的linux版本是incomplete的, 所以有些功能可能还是不支持地. 当然前面的两个有没有windows/mac的版本还不知道呢:)

May 15th, 2009ie css selector issue

最近写了一些html, 遇到一个很郁闷的问题, 大致是下面的, 我写了一些链接, 大致如下:

<a class="actionlink" onclick=’myfunction(this);'>mylink</a>

为它写了一个css, 如下:

a.actionlink {

    display:block;

    text-decoration: none;

    color:  #62C2CC;

    outline: none;

    }

a.actionlink:hover {

    display:block;

    color: blue;

    text-decoration: underline;

    }

a.actionlink:visited {

    display:block;

    color:  $62c2cc;

    }

a.actionlink:active {

    display:block;

    color:  red;

    }

链接在firefox下面显示的完全正常, 可是在ie7/8下却死活无法正常显示在鼠标移上去时的颜色, 网上找了很久, 看到一些相关的ie8的bug, 像css selector的问题, 这里有一个列表, 本来以为其中的一个css子对象选择符的bug是这个有关, 但是我已经把它改成了直接的类, 所以应该不是那个问题.

两天郁闷的尝试都没有成功, 今天再看相关的文档, 在看到选择符的时候, 偶然看到这么一句:

The document language determines which elements are hyperlink source anchors. For example, in HTML4, the link pseudo-classes apply to A elements with an "href" attribute. Thus, the following two CSS 2.1 declarations have similar effect:

a:link { color: red }
:link  { color: red }
这回忽然注意到其中红色的部分, 难道真和这个有关? 于是试着加上href="#", 之后, 果然一切都好了!……
<a href=”#” class="actionlink" onclick=’myfunction(this);'>mylink</a>

April 22nd, 2009闪屏问题

今天又接到了一个问题:-| 最近的问题有点多哈!

问题是这样的, 朋友的一个程序(VC++编写)它会在上面显示一个背景图片和自己绘制的一个计时的时间显示表, 程序在开发的阶段运行一切正常, 但是客户反映讲此程序运行的相当不稳定, 总是在过了一段时间后会出现背景变灰, 并且窗口界面开始不停的闪烁. 朋友经过测试也发现有类似的状况, 在朋友的机器上程序大概运行三分钟后就会出现客户说的情况, 据朋友描述说有时也两分钟左右就会出现闪烁的情况, 只是这种情况比较少, 但三分钟是一个比较保险的使问题重现的时间.

刚听到这个问题, 比较没有头绪, 找不到问题, 于是让朋友把相关的代码发过来, 看了代码心中大概有了一个概念:

  1. 界面完全是由自己的代码绘制的,
  2. 为了显示计时器的时间, 在代码里加了一个计时器, 并在onTimer ()时重新绘制整个窗口
  3. 绘制时钟是通过装载对应的位图绘制数字的

看到这里, 心里大概能猜测到了一个可能的原因: 句柄泄漏, 一想到这一点, 所有的问题都可以解释了: 极有可能是在窗口绘制的代码里出现了句柄泄漏的情况, 在刚开始的时候没有问题, 但由于每秒钟都会定时绘制一下界面, 所以随着时间的推移, 泄漏的句柄数越来越多 — 这样当所有可用的句柄都泄漏完之后, 当再次尝试申请句柄资源以创建相关的位图对象的时候就会失败, 这样就会导致画不出相关的图片来, 于是整个界面就会是灰/白色了, 至于闪烁的问题, 应该是由于每次刷新失败导致的问题. 这么一讲还真可以讲的通. 再仔细看一下代码吧:) 下面是一段类似于朋友ontimer函数的代码:

void CMyDlg::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == MY_TIMER_EVENT)
    {
        //
        // draw the clock
        //
         CDC *pDC = GetDC();
        CDC CompatibleDC;
        CompatibleDC.CreateCompatibleDC(pDC);
        CBitmap bmp;
        bmp.LoadBitmap(IDB_DIGITS);
        BITMAP bitmap;
        bmp.GetBitmap(&bitmap);
        CompatibleDC.SelectObject(&bmp);
        //
        // draw minutes/seconds, using hte digits in the bitmap...
        //
        pDC->StretchBlt(200,565,87,135, &CompatibleDC,(bitmap.bmWidth/12) * minute,
                0, bitmap.bmWidth/12,bitmap.bmHeight,SRCCOPY);

        ReleaseDC(pDC);
    }
}

初看貌似没有什么问题, dc对象被正常释放了, 不过再仔细看一下! 注意这里的CBitmap以及selectObject, 这里是有泄漏的,或许你会说CBitmap的析构函数会释放它使用的gdi 对象, 在msdn里的LoadBitmap的方法说明里也是这么讲的:

The loaded bitmap is attached to the CBitmap object.

If the bitmap identified by lpszResourceName does not exist or if there is insufficient memory to load the bitmap, the function returns 0.

You can use the CGdiObject::DeleteObject function to delete bitmap loaded by the LoadBitmap function, or the CBitmap destructor will delete the object for you.

但是请注意下面的一行!

Caution:

Before you delete the object, make sure it is not selected into a device context.

这里提到请注意在删除一个对象之前请确认它没还没当前的设备上下文选中! 如果删除一个当前被使用的对象会有什么后果? 虽然没有找到文档, 但照猜测的话那就是删除失败! 所以毫无疑问每次都会导致一个CBitmap的gdi对象的泄漏! 所以修好这个问题应该是在使用完这个bitmap对象后再使用selectObject把选择原来的gdi 对象, 原来的gdi对象可以通过CompatibleDC.SelectObject(&bmp);的返回值得到. 所以修改后的代码应该是这样:

        CBitmap* oldObject = CompatibleDC.SelectObject(&bmp);

        //
        // draw minutes/seconds, using hte digits in the bitmap...
        //
        pDC->StretchBlt(200,565,87,135, &CompatibleDC,(bitmap.bmWidth/12) * minute,
                0, bitmap.bmWidth/12,bitmap.bmHeight,SRCCOPY);
        pDc->SelectObject (oldObject);  //select the old object.!
        ....

这样应该就可把这个问题修好啦! (没有机会测试, 但应该没有问题…).

再另外, 朋友告诉我他们找了一个人修了一下, 他们的方法是把那个CBitmap对象变成一个全局对象, 这样只要load一次, 所以也不会出现严重的泄漏情况(顶多一个gdi 对象, 另外效率相对每次都要load要好一些), 所以也可以算一个方法:)


© 2007 pangwa's Blog | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress