浏览模式: 标准 | 列表

因为C#中没有提供 Switch on Type 的功能,因此要判断类型通常会用一长串的if else,当然这种写法的问题是不够高效,且不够美观。因此 C# 中对常见类型提供了一组枚举值,也就是 TypeCode(这个枚举类型在.NET for Windows App Store 中居然被取消了,大概是因为DBNull这个类型本身也被取消了,这个值没有对应的类型了,干脆连TypeCode就一起取消了)。可以使用 Type.GetTypeCode 方法来得到类型对应的枚举值,之后对得到的枚举值进行switch操作。但需要注意这里有一组特殊的类型,那就是枚举类型,它的返回值是该枚举类型对应的数字类型,而不是Object类型。

因此如果你想区分枚举类型和数字类型的话,记住不要直接用这个方法,至少要先用 IsEnum 来判断一下。否则你可能会错把枚举了类型当成数字类型进行处理。

1月
03

C# 中 is 和数组的问题

在C#中,我们可以很方便的在object对象上使用is运算符来快速的判断对象的类型,is运算符还具有在子类对象上判断是否属于某个父类或者接口的能力,与之等价的判断是,使用GetType方法获得类型后,使用父类或接口上的IsAssignableFrom方法来判断。后者速度比使用 is 运算符要慢很多。所以通常我们在存在对象和已知要判断的类型时优先使用is运算符。

但是is运算符在判断数组类型时却有一个bug,当然这个bug也许是设计问题,但是它确实可能跟我们期望的行为不一致。

这个bug可以这样描述,当一个整数数组对象(byte[], sbyte[], short[], ushort[], int[], uint[], long[], unlong[]这八种)被赋值给一个object对象时,在这个object对象上使用is方法判断时,在等位长整数类型上判断的结果总为True,与类型是否有符号无关。例如:

C#代码
 
  1. int[] a = new int[] { -3, -2, -1, 0, 1, 2, 3 };  
  2. Console.WriteLine(a is int[]);  
  3. Console.WriteLine(a is uint[]);  
  4. object o = a;  
  5. Console.WriteLine(o is int[]);  
  6. Console.WriteLine(o is uint[]);  
的输出结果为:

True
False
True
True

前三个输出是我们期望的,但是最后一个输出的True却是出乎意料的。

这里如果把 int[] a 换成 uint[] a 也是一样,例如:

C#代码
 
  1. uint[] a2 = new uint[] { 0, 1, 2, 3 };  
  2. Console.WriteLine(a2 is int[]);  
  3. Console.WriteLine(a2 is uint[]);  
  4. o = a2;  
  5. Console.WriteLine(o is int[]);  
  6. Console.WriteLine(o is uint[]);  
的输出结果是:

False
True
True
True

第三个输出结果True是我们意料之外的。

同样byte[]和sbyte[]也存在这种关系,short[]和ushort[],long[]和ulong[]之间同样也存在这种关系。

所以使用is在这几种数组的object对象上做判断时,一定要注意这个问题,否则很容易掉进这个陷阱中去。

11月
27

系统汉化

 要对系统进行汉化与其说是个技术活,不如说是个力气活,但确实又少不了细心。系统汉化可以分为应用汉化、框架汉化、Luna汉化和其它部分汉化。

应用汉化

ROM中预先安装好的应用都在rootfs/usr/palm/applications中,对每个应用汉化时要注意,不同的程序编写方式,汉化方式也不一样。mojo和enyo的程序在多国语言化方面有些差异。

MOJO程序汉化

mojo程序汉化时,在相应的包名目录下,建一个resources/zh/cn目录。

如果要汉化桌面图标上的显示文字,全局搜索等内容,你需要建立一个appinfo.json的文件,这个文件内容可以直接把从软件包中的这个文件复制过去然后修改,修改时需要注意,图标位置,执行文件的位置等内容,一定要加上../../../这样的路径,否则这些文件找不到。

如果你要汉化js中的那些为实现多国语言已经做了处理的字符串(就是用$L()包含的那些字符串),那你需要再建立一个strings.json文件,其中的内容格式为:

  1. {  
  2.     "OK""确定",  
  3.     "Cancel""取消"  
  4. }  

如果字符串中包含一些特殊字符,一定要注意要用json的转义方式来写。

还有些需要汉化的字符串是在views中的html中的,这些内容的汉化,只需要在这个目录下建立对应的目录,将原始的html复制到这些目录下,对其进行汉化即可。

有时候汉化还涉及到css的一些调整,同样把css文件复制过来做修改就可以啦。

ENYO程序汉化

enyo的程序汉化相对来说简单些。

对appinfo.json的汉化与mojo相同。

对字符串的汉化只需要在resources下建一个zh_cn.json的文件就可以了,内容格式跟mojo的strings.json格式相同。

enyo没有views这种东西,所以就不用管了。

框架汉化

在rootfs/usr/palm/frameworks下的许多框架文件也需要汉化。具体那些文件需要汉化就不说了,可以参考其它多语言化文件来处理。

frameworks这里面有些目录在一些比较新版本的原始ROM中就已经包含了汉化内容,比如mojo,mojo2,enyo这些目录,所以如果发现他们里面原本就有汉化内容的,就不需要重复汉化了。

LUNA汉化

在rootfs/usr/lib/luna下,有部分资源是需要汉化的,其中包括luna-network和system。汉化的内容跟汉化的方法跟mojo应用程序相同。

其中关于信标的汉化最主要的部分也在这里。在rootfs/usr/lib/luna/system/luna-systemui/resources/zh/cn/strings.json中,添加:

  1. "CHINA  MOBILE""中国移动",  
  2. "CHN-UNICOM""中国联通",  
  3. "China Telecom""中国电信",  
  4. "China Unicom""中国联通",  
  5. "CMCC""中国移动",  

就可以实现对信标的汉化了。注意上面"CHINA  MOBILE"中间是两个空格,而不是一个。

但要实现更完美的信标汉化,还需要在:

rootfs/usr/palm/applications/com.palm.app.phoneprefs/resources/zh/cn/strings.json

rootfs/usr/palm/sysmgr/localization/zh_cn/strings.json

里面都添加上这一段内容。

对于一卡多号的用户来说,上面这些数据可以能对您的信标中文化无效,因为一卡多号的卡里面的信标内容跟上面的是不同的,有同学反映他的一卡多号的信标后面还带有手机号,所以对于这种卡就没办法做统一的汉化了,但是使用者可以按照这种方法,把自己那些特殊的信标加到这里面。

其它部分汉化

在rootfs/usr/palm/public和rootfs/usr/palm/sysmgr里面也有一点资源需要汉化。至于那些文件,可以参考这里面的其它语言的资源。

这里主要提一点关于启动器中那些页面标题的汉化,这些标题的汉化可以在rootfs/usr/palm/sysmgr/localization/zh_cn/strings.json中添加上。

而且不但可以添加默认的那几个(Applications、Extras、System这些),你还可以添加用户可能会建立的新页面的标题,比如Games、News等,这些添加之后,用户在建立具有这些名称的页面之后,当再次重启系统时候,这些英文名在中文界面下也会自动变成中文了。

最后还要强调一下,汉化是个细心的活,要想完美汉化,对每个词每个句子甚至每个缩写都要字斟句酌才能得到完美的结果。

举个例子,比如在日历的汉化中,有几个英文序数词结尾的汉化,这些序数词后缀分别是"nd"、"rd"、"st"、"th",如果你不了解上下文,当看这几个词你可能不知道什么意思(但如果你够聪明的话,当看到这几个摆在一起时应该也会想到)。我们知道在中文里序数词和基数词是没区别的(如果非要说有区别的话,那区别就是中文的序数词是在基数词前面加一个“第”字),那么在汉化这几个词时,我们就应该把它们汉化为空字符串,而不应该原样保留(因为我们没办法把后缀汉化改成前缀汉化,所以不能把它们汉化成“第”)。

类似的地方还有好多,这里仅仅是举个例子。从这里我们可以看出,汉化的确不是一件容易的事情,所以,在这里我要向一直以来提供系统汉化包的开发者们表示崇高的敬意!

汉化包的打包

汉化包在打包时,如果考虑到集成到ROM中,并且可以方便卸载的话,最好的方式是把这些新添加的汉化资源单独拿出来打成一个包,这些资源在包里面就以原始路径存在,不需要postinst、postrm之类的脚本,直接打包之后,放在<carrier>.tar中就可以啦。

这样做的好处是,便于汉化包的卸载和升级。

但是这种包直接提供给用户安装的话,用户还需要一定的Linux命令行知识,能够自己使用ipkg命令来安装卸载包才行。因为这些包是不能通过preware、WQI来安装的,否则安装位置是不对的。

现在直接提供给用户安装的汉化包,大部分都是以应用程序方式来打包,然后通过postinst方式来将汉化资源再复制到相应的汉化目录下。这种汉化包对用户来说,便于使用preware、WQI来安装,但是却不方便卸载,也不方便集成到ROM中。

所以,两种方式各有利弊,根据具体情况来做选择就行了。

11月
26

将ROM分解为IPK

 前面6节基本上已经把ROM定制的具体过程都讲完了。从这节开始,我们来讨论一点高级内容。不过本人水平有限,以下内容算是抛砖引玉了。

前面在ROM文件结构一节中,我们已经提到,rootfs下的大部分文件都是预安装好的包,那我们有没有可能将这些包都还原为ipk呢?

当然可以。

下面我们就来看一下这个过程该如何实现。

首先我们需要把ROM分解,分解的工具最方便的还是使用MetaDoctor里面的unpack-doctor这个脚本。在Linux下,我们最好是用root帐号来执行这个命令,以保证包中文件的权限不会改变。

展开之后,我们可以在rootfs/usr/lib/ipkg/info中找到所有已安装的包的control文件,因为每个包都有一个对应的control,所以我们以这个control文件为依据,就可以列出系统内所有的包。

之后循环读取这些control文件名,然后通过它来获取包名,然后用包名为该包建立一个目录,在其中再创建CONTROL和data这两个目录,然后把control复制(或移动)到CONTROL目录下,然后把该包对应的preinst、postinst、prerm、postrm脚本也都统统复制(或移动)到CONTROL目录下,最后再根据list文件中的内容,将该包中包含的文件及其目录复制(或移动)到data目录下,最后使用ipkg-build脚本,对该目录进行打包就可以啦。

下面是将ROM分解为ipk文件的完整脚本:

  1. #!/bin/sh  
  2.   
  3. [ -f $1 ] || exit 1  
  4.   
  5. currpath=`pwd`  
  6. $currpath/unpack-doctor $1  
  7.   
  8. NAME=`basename $1 .jar`  
  9.   
  10. rm -rf $NAME/ipkgs $NAME/build  
  11. mkdir -p $NAME/ipkgs $NAME/build  
  12.   
  13. cd $NAME/ipkgs  
  14.   
  15. ROOTFS=$currpath/$NAME/rootfs  
  16. INFOPATH=$ROOTFS/usr/lib/ipkg/info  
  17.   
  18. ls $INFOPATH/*.control | while read control; do  
  19.     package=`basename $control .control`  
  20.     PACKAGE=$currpath/$NAME/build/$package  
  21.     CONTROL=$PACKAGE/CONTROL  
  22.     data=$PACKAGE/data  
  23.     mkdir -p $CONTROL $data  
  24.     cp $control $CONTROL/control  
  25.     if [ -f $INFOPATH/$package.preinst ]; then  
  26.         mv $INFOPATH/$package.preinst $CONTROL/preinst  
  27.     fi  
  28.     if [ -f $INFOPATH/$package.postinst ]; then  
  29.         mv $INFOPATH/$package.postinst $CONTROL/postinst  
  30.     fi  
  31.     if [ -f $INFOPATH/$package.prerm ]; then  
  32.         mv $INFOPATH/$package.prerm $CONTROL/prerm  
  33.     fi  
  34.     if [ -f $INFOPATH/$package.postrm ]; then  
  35.         mv $INFOPATH/$package.postrm $CONTROL/postrm  
  36.     fi  
  37.     if [ -f $INFOPATH/$package.list ]; then  
  38.         cat $INFOPATH/$package.list | while read filename; do  
  39.             pathname=`dirname "$filename"`  
  40.             mkdir -p "$data/$pathname"  
  41.             mv -f -T "$ROOTFS/$filename" "$data/$filename"  
  42.         done  
  43.         $currpath/ipkg-build $PACKAGE  
  44.     fi  
  45. done  
  46.   
  47. cd $currpath  

把这个保存为一个脚本,然后在同一个目录下放上unpack-doctor和ipkg-build脚本,然后再把要展开的ROM放在这个目录下,这个脚本的目录下运行脚本后面跟上ROM名称,就可以将ROM分解为ipk了。

理解了以上脚本之后,根据该原理,就也可以很方便的完成ROM杂交,ROM内置安装包移除等脚本了。这些脚本的编写就看各位高手的啦。

11月
26

集成安装IPK

 完成了ipk的打包之后,我们需要将修改过的或者新添加的ipk包加入到我们自己要定制的ROM当中。但ipk包的安装位置是有区别的,一种是安装到root的ipk包,另一种是安装到/media/cryptofs/apps下的ipk包。

安装到root的ipk包,有两种集成方式,一种是预安装方式,一种是放在<carrier>.tar中(<carrier>表示att、wr、verizon等)。

预安装方式采用跟原始ROM中那些预安装的包一样的方法,MetaDoctor里面有两个选项EXTRA_ROOTFS_IPKGS和EXTRA_ROOTFS_TARBALL,把事先修改过的包手动展开按照ROM文件结构打包成tar文件,然后放在跟Makefile相同的目录下,然后设定好这个EXTRA_ROOTFS_TARBALL这个参数,然后把跟这个tar里面有关的包名放在EXTRA_ROOTFS_IPKGS这个参数里,就可以了。这种方式比较麻烦,而且容易出错,修改之后对root分区镜像的影响是永久性的,所以,这不是推荐的方式。

而放在<carrier>.tar中则非常方便。但是仍然有需要注意的问题,如果ROM中已经有预安装的同名的包,需要先手动从rootfs镜像中删除。删除方法不难,但比较繁琐:

到rootfs/usr/lib/ipkg/info下找到跟要删除的包名相关的所有文件(control,list,preinst等),根据这里面的list文件中的内容,找到这里面的这些文件一一删除,修改rootfs/md5sums.tar里面的md5sums文件,将其中跟这个包有关的所有内容删除。然后根据control文件中所描述的构架到rootfs/usr/lib/ipkg/lists中找到对应构架的那个文件,打开它将其中关于这个包的部分删除掉。打开rootfs/usr/lib/ipkg/status,将其中跟这个包有关的内容删除掉。把rootfs/usr/lib/ipkg/info中的跟这个包有关的所有文件删除。最后再次修改rootfs/md5sums.tar里面的md5sums文件,将修改过的rootfs/usr/lib/ipkg/lists/oe-<arch>和rootfs/usr/lib/ipkg/status这两个的md5sum值重新计算后更新。

以上步骤可以通过写一个脚本来自动完成,有兴趣的同学可以自己来写。

另一种是安装到/media/cryptofs/apps中的包,这些包实际上并不是在刷机过程中安装的,而是在刷机之后配置完毕的第一次启动时安装的。

在webOS 2.1中,系统给出了一个这样的例子。那就是系统里内置的app-ipkgs这个包。这个包在webOS 2.2.x和webOS 3.x中有更新,更新之后的安装方式有些变化,我们暂时不管,我们先来看看webOS 2.1中的app-ipkgs这个包是怎么做的。

这个包包含2个部分,一部分是/usr/palm/ipkgs这个目录下的一些安装包、图标和一个manifest.json文件,另一部分是/etc/event.d/app-install这个安装脚本。

/usr/palm/ipkgs下的安装包没什么好说的,单说manifest.json这个文件,这里面是安装包的一些包信息,如果你要安装的包的信息不在这个文件里,也没有什么太大影响。如果在这个文件里,在软件管理器中就看不到这个软件的删除选项。但在启动器中仍然可以通过按住白方块点图标删除。

我们重点看一下/etc/event.d/app-install这个脚本。

2.1的这个脚本很简单,就是将/usr/palm/ipkgs下的所有ipk列出来然后通过循环方式执行安装。

在安装时首先将安装文件复制到/media/internal/downloads下,其实复制到/media/internal的其它任何自建的目录下都可以,这样做是因为安装时,安装位置是需要可写入的,因为在安装过程中安装器会自动创建一个tmp目录。另外,如果要保证安装目录可写,也可以用rootfs_open –w的方法。这样可以省去文件的复制过程。

然后通过:

ipkg –o /media/cryptofs/apps install $package

命令安装。

最后通过:

luna-send -n 1 palm://com.palm.applicationManager/forceSingleAppScan '{"id":"'$pkg'"}'

来使安装的软件生效(桌面上就可以看到图标了)。

这样的一个安装过程,对于安装没有安装依赖顺序,没有postinst脚本的ipk来说是足够了。

但对于带有postinst脚本、prerm等脚本的应用来说还是有些问题的。因为ipkg –o 方式安装时,不会自动执行postinst脚本,这就使得带有postinst脚本的安装程序不能被完全安装。

我们可以在ipkg –o 之后加上这样一段:

  1. #check if install succeeded, then run postinst if needed  
  2. if [ -f $APPS/usr/lib/ipkg/info/$pkg.control ] ; then  
  3.     if [ -f $APPS/usr/lib/ipkg/info/$pkg.prerm ] ; then  
  4.         if [ ! -f $APPS/.scripts/$pkg/pmPreRemove.script ] ; then  
  5.             /bin/mkdir -m 777 -p $APPS/.scripts/$pkg  
  6.             /bin/cp -f $APPS/usr/lib/ipkg/info/$pkg.prerm $APPS/.scripts/$pkg/pmPreRemove.script  
  7.         fi  
  8.     fi  
  9.     if [ -f $APPS/usr/lib/ipkg/info/$pkg.postinst ] ; then  
  10.         echo "$UPSTART_JOB: Running $pkg.postinst" | logger  
  11.         export IPKG_OFFLINE_ROOT=$APPS ; /bin/sh $APPS/usr/lib/ipkg/info/$pkg.postinst  
  12.     fi  
  13. fi  

就实现了这些安装脚本的处理。

但是对于有安装依赖顺序的ipk来说,通过for循环目录可能无法保证正确的安装顺序,所以我们可以人为的来写一个安装文件列表,比如叫packages(其实叫什么都行),里面一行一个ipk文件名,例如:

  1. com.palm.app.findapps_2.0.23300_all.ipk  
  2. org.webosinternals.preware_1.8.5_arm.ipk  
  3. org.webosinternals.diffstat_1.45-1_armv7.ipk  
  4. org.webosinternals.lsdiff_0.3.1-1_armv7.ipk  
  5. org.webosinternals.patch_2.5.9-4_armv7.ipk  
  6. org.webosinternals.unzip_6.0-1_armv7.ipk  
  7. org.webosinternals.zip_3.0-1_armv7.ipk  
  8. ca.canucksoftware.js-service-framework_1.0.1_all.ipk  
  9. ca.canucksoftware.filemgr_2.0.7_all.ipk  
  10. ca.canucksoftware.internalz_1.5.0_all.ipk  
  11. org.webosinternals.patches.misc-unthrottle-download-manager_2.1.0-160_all.ipk  

这样一个格式,只要这个文件里的包安装顺序正确,就可以用for循环来读取这个文件来保证顺序了。

然后还有一点小问题,就是如果安装的程序里包含有服务,例如上面例子中的filemgr,这样安装之后,可能服务无法自动启动,原因是在/var/palm/ls2/roles/prv和pub下没有生成服务规则文件。本来这些文件应该是在程序安装之后,系统根据usr/palm/services/包名/services.json自动生成的,但既然系统没有生成,那我们就自己创建一下这两个文件,加入下面的代码可以完成规则文件的创建:

  1.         if [ -f $APPS/usr/palm/services/$pkg/services.json ] ; then  
  2.             echo "{  
  3.     \"role\": {  
  4.         \"exeName\":\"js\",  
  5.         \"type\": \"regular\",  
  6.         \"allowedNames\": [\"$pkg\"]  
  7.     },  
  8.     \"permissions\": [  
  9.         {  
  10.          \"service\":\"$pkg\",  
  11.          \"inbound\":[\"*\"],  
  12.          \"outbound\":[\"*\"]  
  13.         }  
  14.     ]  
  15. }" > /var/palm/ls2/roles/prv/$pkg.json  
  16.             chmod +x /var/palm/ls2/roles/prv/$pkg.json  
  17.             cp /var/palm/ls2/roles/prv/$pkg.json /var/palm/ls2/roles/pub/$pkg.json  
  18.         fi  

最后,我们修改之后的完整脚本是这样子的:

  1. # -*- mode: shell-script; -*-  
  2.   
  3. start on stopped configurator  
  4.   
  5. script  
  6.     rootfs_open -w  
  7.   
  8.     APPS=/media/cryptofs/apps  
  9.     IPK_DIR=/usr/palm/ipkgs  
  10.   
  11.     for package in $(cat $IPK_DIR/packages)  
  12.     do  
  13.         echo "$UPSTART_JOB: attempt to install $package" | logger  
  14.   
  15.         #call luna-send to begin installation  
  16.         ipkg -o $APPS install $IPK_DIR/$package | logger  
  17.   
  18.         # Get the pkg name from the file name.  
  19.         local bname=${package##*/}  
  20.         local rname=${bname%.ipk}  
  21.         local pkg=${rname%%_*}  
  22.   
  23.         if [ -f $APPS/usr/palm/services/$pkg/services.json ] ; then  
  24.             echo "{  
  25.     \"role\": {  
  26.         \"exeName\":\"js\",  
  27.         \"type\": \"regular\",  
  28.         \"allowedNames\": [\"$pkg\"]  
  29.     },  
  30.     \"permissions\": [  
  31.         {  
  32.          \"service\":\"$pkg\",  
  33.          \"inbound\":[\"*\"],  
  34.          \"outbound\":[\"*\"]  
  35.         }  
  36.     ]  
  37. }" > /var/palm/ls2/roles/prv/$pkg.json  
  38.             chmod +x /var/palm/ls2/roles/prv/$pkg.json  
  39.             cp /var/palm/ls2/roles/prv/$pkg.json /var/palm/ls2/roles/pub/$pkg.json  
  40.         fi  
  41.   
  42.         #check if install succeeded, then run postinst if needed  
  43.         if [ -f $APPS/usr/lib/ipkg/info/$pkg.control ] ; then  
  44.             if [ -f $APPS/usr/lib/ipkg/info/$pkg.prerm ] ; then  
  45.                 if [ ! -f $APPS/.scripts/$pkg/pmPreRemove.script ] ; then  
  46.                     /bin/mkdir -m 777 -p $APPS/.scripts/$pkg  
  47.                     /bin/cp -f $APPS/usr/lib/ipkg/info/$pkg.prerm $APPS/.scripts/$pkg/pmPreRemove.script  
  48.                 fi  
  49.             fi  
  50.             if [ -f $APPS/usr/lib/ipkg/info/$pkg.postinst ] ; then  
  51.                 echo "$UPSTART_JOB: Running $pkg.postinst" | logger  
  52.                 export IPKG_OFFLINE_ROOT=$APPS ; /bin/sh $APPS/usr/lib/ipkg/info/$pkg.postinst  
  53.             fi  
  54.         fi  
  55.   
  56.         echo "$UPSTART_JOB: attempt to scan $pkg" | logger  
  57.   
  58.         # Have the applicationManager rescan the pkg so that it will   
  59.         # show up in the launcher.  
  60.         returnVal=$(luna-send -n 1 palm://com.palm.applicationManager/forceSingleAppScan \  
  61.             '{"id":"'$pkg'"}' 2>&1)  
  62.   
  63.         logger "$UPSTART_JOB: returnVal is $returnVal"  
  64.     done  
  65. end script  

现在我们可以把这个包单独拿出来,然后把rootfs下的这个预先安装的这个包删掉。最后修改之后,放在<carrier>.tar里面就可以啦。

当然,更好的方法是,不要动系统的这个app-ipkgs。自己创建一个类似的包,脚本按照自己设置的目录来写。最后单独打一个包,名字不要跟系统已有的包冲突,最后放在<carrier>.tar中就可以了。自己创建类似安装包的好处是,它可以减少你对rootfs的修改,保证系统自带的升级程序不会被破坏,另外还能适用于1.x、2.2.x和3.x系统,而不仅仅局限于2.1.x系统。