当前位置

qyt的博客

开启真机的View Server引入HierarchyViewer/By写monkeyrunner自动化测试脚本

其实相关文章网上也有不少了,不过在真机上开启View Server的中文文章好像只有一篇,前段时间按照这篇文章的内容,并结合英文源文去hack我的Nexus S(4.1.2)也走了一点弯路。现在总结一下我的步骤(其实有相当一部分拷贝了这篇,衷心感谢原文作者)。并写点在开启View Server之后monkeyrunner的脚本。

先交待一下背景,monkeyrunner作为自动化测试Android系统工具在某些情况下还是比Robotium易用一些,不过monkeryrunner判断测试结果是否正确的方法是把实际测试中的截屏与预先截好的正确的屏跟做比对!这个办法不够灵活。假如返回结果会显示在一个文本框中,我从文本框里取出字符串能直接跟预期的字符串比较,这样就省事多了。

Android SDK自带一个工具叫做monitor,它里面的Hierarchy Viewer可以看到app的UI结构、控件属性等等。monkeyrunner有一个类By,通过By可以在代码中根据控件ID定位到该控件从而写更有针对性代码(比如点击按钮、比如获取文本框中的字符串)。

可是出于安全考虑,Hierarchy Viewer只能连接Android开发版手机或是模拟器。只有当设备或模拟器上启动一个叫做View Server的服务,Hierarchy Viewer才能与其进行socket通信,才能看到app的“View”。而绝大多数商业手机是无法开启View Server的,所以Hierarchy Viewer也就无法连接到普通的商业手机。而By又依赖于Hierarchy Viewer,所以如果想在普通的商业手机上通过控件ID去做一些操作,连接模拟器运行通过的脚本连接真机运行是会抛错的。

不过小米手机是个例外,通过执行如下命令可以轻易开启它的View Server:
adb shell service call window 1 i32 4939
然后通过执行如下命令判断是否开启View Server:
adb shell service call window 3
若返回值是:Result: Parcel(00000000 00000001 '........') 说明View Server处于开启状态
若返回值是:Result: Parcel(00000000 00000000 '........') 说明View Server处于关闭状态
如果想关闭View Server执行如下命令:
adb shell service call window 2 i32 4939

除了小米手机之外,别的手机能不能开启View Server?经过一番调查和实践,其实只要是root,并且装有busybox的手机,通过修改手机/system/framework中的某个文件,就能够开启View Server。

下面就是我总结的开启View Server的步骤(提醒:如果照我的步骤导致你的手机变砖,本人概不负责):

1.准备工作

a.解锁手机,刷入第三方Recovery。这一步不是开启View Server必须要做的。但是万一手机通过正常方式启动不了了,可以通过第三方Recovery里的restore功能恢复手机系统,当然前提是在修改系统文件前先通过backup功能做一个备份。

b.root手机。root的作用是获取对手机系统文件的读写权限,这样你就可以修改那个不允许打开View Server的系统文件了。

c.在手机中安装BusyBox应用。我们在给自己生成的odex文件签名时会用到它。

d.用第三方Recovery备份手机系统。这一步不是必须步骤。

e.在D盘下创建hack文件夹,下载baksmali-1.4.2.jarsmali-1.4.2.jarzip.exedexopt-wrapper这些后面要用到的工具并保存在D:\hack下面。

2.开始hack (再次提醒:请确保把下面每个步骤所有文字全部仔细看完后再开始操作)

a.将手机通过USB连接PC,确保adb服务运行正常。

b.备份手机上/system/framework/中的文件至PC。备份的时候请确保PC上保存备份文件的文件夹结构与手机中的/system/framework相同,比如先在D盘上创建hack\system\framework的文件夹结构,然后运行
adb pull /system/framework D:\hack\system\framework

c.进入adb shell,输出BOOTCLASSPATH:
echo $BOOTCLASSPATH
然后将输出的路径先暂时存起来。我的是(每个机器的$BOOTCLASSPATH都不一定一样):
/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar

d.在命令行窗口中进入D:\hack,然后运行baksmali反编译\system\framework下的services.odex文件:
java -jar baksmali-1.4.2.jar –x -a <api level> –c <local bootclasspath> system\framework\services.odex
参数解释:https://code.google.com/p/smali/wiki/DeodexInstructions
想特别说明的是“-a”后跟的数字,表示你系统的API Level(与你的系统版本有关)。系统版本和API Level的对照关系如下:

这一步在我的机器(version 4.1.2)上的命令是:
java -jar baksmali-1.4.2.jar -x -a 16 -c system\framework\core.jar:system\framework\core-junit.jar:system\framework\bouncycastle.jar:system\framework\ext.jar:system\framework\framework.jar:system\framework\android.policy.jar:system\framework\services.jar:system\framework\apache-xml.jar system\framework\services.odex
此步成功的话,在D:\hack下,会有个out文件夹生成。
注意,-c后面跟的是本地备份的jar包路径,把上一步暂存的路径中system前面的“/”去掉,把其它的“/”换成“\”。
这里顺便解释一下dex文件、odex文件和smali文件:

  • dex文件:dex是Dalvik VM executes的全称,即Android Dalvik执行程序,并非Java的字节码而是Dalvik字节码,16进制机器指令。
  • odex文件:将dex文件依据具体机型而优化,形成的optimized dex文件,提高软件运行速度,减少软件运行时对RAM的占用。
  • smali文件:将dex文件变为可读易懂的代码形式,反编译出文件的一般格式。

e.用Eclipse打开out\com\android\server\wm\WindowManagerService.smali文件查找.method private isSystemSecure()Z这个函数,在这段代码的倒数7,8行“:goto_21”和“return v0”之间加入“const/4 v0, 0x0”一行。
.method private isSystemSecure()Z函数最后几行变为:
if-eqz v0, :cond_22

const/4 v0, 0x1

:goto_21
const/4 v0, 0x0
return v0

:cond_22
const/4 v0, 0x0

goto :goto_21
.end method

f.现在运行smali,重新编译:
java -jar smali-1.4.2.jar -o classes.dex out
这时候,应该在D:\hack文件夹中出现了classes.dex文件

g.用zip工具把生成的classes.dex打成jar包
zip.exe services_hacked.jar classes.dex

h.进入adb shell,输入su然后回车,获得ROOT权限

i.接着输入mount | grep /system查看哪个分区挂载了/system,例如我的是:
/dev/block/platform/s3c-sdhci.0/by-name/system /system ext4 ro,relatime,barrier=1,data=ordered 0 0

j.接着输入以下命令重新挂载/system,并更改/system权限(请将“/dev/block/platform/s3c-sdhci.0/by-name/system”替换成你的/system挂载分区):
mount -o remount /dev/block/platform/s3c-sdhci.0/by-name/system /system
这一步的作用是为了后面的p步能够将/system/framework里的services.odex替换掉。

k.再次输入mount | grep /system 确认/system已经改成可写的了(以前是“ro”,现在是“rw”)

l.将services_hacked.jar和dexopt-wrapper复制到手机的/data/local/tmp文件夹中
adb push D:\hack\services_hacked.jar /data/local/tmp
adb push D:\hack\dexopt-wrapper /data/local/tmp

m.进入adb shell,输入su后,将dexopt-wrapper的权限改为777
chmod 777 /data/local/tmp/dexopt-wrapper

n.cd到/data/local/tmp文件夹下,运行:
./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex <c步暂存的bootclasspath,但要排除掉“:/system/framework/services.jar”>
这一步在我的机器上的命令是:
./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/apache-xml.jar
这样,便在/data/local/tmp文件夹中生成了services_hacked.odex这个文件

o.给我们自己生成的services_hacked.odex签名:
busybox dd if=/system/framework/services.odex of=/data/local/tmp/services_hacked.odex bs=1 count=20 skip=52 seek=52 conv=notrunc
参数解释:

  • if - input file
  • of - output file
  • bs - block size (1 byte)
  • count - number of blocks
  • skip - input file offset
  • seek - output file offset
  • conv=notrunc - don’t truncate the output file.

p.将/system/framework里的services.odex替换成我们自己制作的services_hacked.odex
dd if=/data/local/tmp/services_hacked.odex of=/system/framework/services.odex
稍过一会,手机就会自动重启

q.成功重启后,用以下命令开启View Server:
adb shell service call window 1 i32 4939

r.用以下命令查看View Server是否开启:
adb shell service call window 3
返回的值若是Result: Parcel(00000000 00000001 '........'),那么你就成功开启View Server了!

3.灾难恢复

如果你不幸在上一节p步手机重启后进不了HOME,一直处在bootloop状态,不要用拔电池的方式重启手机。这个时候你已经可以使用adb了,在命令行窗口里执行:
adb push D:\hack\system\framework\services.odex /system/framework/services.odex
就可以把之前备份的services.odex再拷回去,这样手机就能进入HOME了。

如果你十分不小心重启了手机,这时候你会发现既进不了HOME也使用不了adb,那就只能进入第三方的Recovery,用之前的备份去恢复手机系统了。

下面的是如何利用HierarchyViewer和By这两个类去灵活完成monkeyrunner的脚本(monkeyrunner的其它基本代码在这里不赘述)。

先假设一个场景,有一个app,打开后有一个按钮,点击这个按钮后,正常情况下会在下面的文本框里返回“ok”。我们需要用代码实现点击这个按钮,然后取得文本框中的返回值与预期结果“ok”做比对。

我们通过前面介绍的Hierarchy Viewer看到app里按钮的ID是“id/button”,文本框的ID是“id/output”。

为了通过控件ID操作手机,我们需要在代码开头import这两个类:
from com.android.monkeyrunner.easy import By
from com.android.chimpchat.hierarchyviewer import HierarchyViewer

然后用下面的代码获得按钮对象:
hierarchyViewer = device.getHierarchyViewer()
viewNodeButton = hierarchyViewer.findViewById("id/button")

用下面的代码获得按钮的中心坐标:
pointButton = HierarchyViewer.getAbsoluteCenterOfView(viewNodeButton)

这个时候pointButton.x是按钮的中心点横坐标,pointButton.y是按钮的中心点纵坐标,可是有了这两个坐标,我们还不能直接用device.touch(x, y, "DOWN_AND_UP")的方式去点这个按钮,因为这个坐标是以开发设计app时手机的屏幕分辨率为基准的,所以我们还需要换算一下才知道在目前的测试手机上按钮的中心坐标是什么。

先通过Hierarchy Viewer查到设计时的屏幕分辨率(比方说是320和533),并在代码中定义:
originalResolutionWidth = 320
originalResolutionHeight = 533

再通过MonkeyDevice的API获得目前的测试手机的屏幕分辨率:
actualResolutionWidth = int(device.getProperty("display.width"))
actualResolutionHeight = int(device.getProperty("display.height"))

然后用下面代码得到目的手机分辨率与开发设计时的分辨率的比值:
xRatio = float(actualResolutionWidth) / originalResolutionWidth
yRatio = float(actualResolutionHeight) / originalResolutionHeight

有了xRatio和yRatio,我们用下面的代码轻而易举就能点到正确的坐标上了:
device.touch(int(pointRegister.x * xRatio), int(pointRegister.y * yRatio), "DOWN_AND_UP")

按钮点下后,我们需要用下面代码获取文本框里的返回值:
viewNodeOutput = hierarchyViewer.findViewById("id/output")
output = viewNodeOutput.namedProperties.get("text:mText").value

这样我们就能用output与预期的“ok”做比对了:
if output == "ok":
    print "success"
else:
    print "fail"

最后加一句关于unittest的,如果想按照python的unittest框架写测试用例,会用到
self.assertEquals(expectedString, actualString)
这样的语句,如果是中文操作系统,跑的时候有可能会出现LookupError: unknown encoding gbk这样的错误,请参考Android 自动化测试学习笔记里面提供的方法解决。

更新20130912:
如果要点击Menu里的Label,会发现所有的id名都一样。这个时候怎么办?也许可以用device.press('KEYCODE_DPAD_UP/DOWN/LEFT/RIGHT')的方法来导航到你需要点击的Label,不过我没有试过。
第三方的包AndroidViewClient,可以通过Label上的Text定位到你想点击的Label。
1.把二进制的jar下载下来并放到sdk\tools\lib下
2.在py文件里from com.dtmilano.android.viewclient import ViewClient
3.然后device, serialno = ViewClient.connectToDeviceOrExit(),启动一个activity,用viewclient = ViewClient(device, serialno)和viewclient.dump()可以拿到所有的控件,然后通过Text就能找到需要的控件了。具体请参考http://blog.csdn.net/jiguanghoverli/article/details/10189401https://github.com/dtmilano/AndroidViewClient/issues/22
如果在运行过程中看到Exception: adb="adb.exe" is not executable. Did you forget to set ANDROID_HOME in the environment?这种错误,把adb.exe放到C:\Windows\system32\下面。
另外,引入这个第三方包还有一个好处是,在测试某些app时不用考虑分辨率的问题了(目前我碰到的是如果点击某个app的menu里的label时不需要考虑分辨率,没有调查到底是因为menu的原因,还是不同的app的开发机制原因)。

更新20130913:
在Windows中文系统下,即使按正文中链接里的办法解决了LookupError: unknown encoding gbk这样的错误,但碰到真正的中文(如果不“解决”,就算assert的是英文,也会报上面的错误)还是会报错,如AssertionError: '\xe5\x9f\x8e\xe5\xb8\x82' != u'\u57ce\u5e02',这时需要把被比较的字符串encode("UTF-8")一下,具体请参考http://1.vb.blog.163.com/blog/static/104546220071113105047729/

Topic: 

记录推推 22 个月的一段对话

【晚上 LP 抱着推推在阳台玻璃前往外面看灯、车什么的,我在旁边凑过来凑过去】
我【伸手】:推推,爸爸抱一下?
推推【用手拨开我的手】:不要不要
我【继续伸手】:推推,爸爸抱一下?妈妈累了!
【推推紧紧地趴在 LP 身上】
LP【可怜地看着我】:推推,爸爸站得高,看得远,让爸爸抱抱好不好?
【推推直起身,看看我,然后向我伸出了手】
【我抱了一会儿,从阳台换到客厅继续晃悠,LP 在旁边凑过来凑过去】
LP【伸手】:推推,妈妈抱一下?
推推【紧紧地趴在我身上】:妈妈累了……

Topic: 

(13)Permission denied 或者 403 Forbidden

这两天接了个任务,调研 code review 工具。

前两天在 centos 上折腾 Review Board,rb-site 完成后,启动 httpd,error log 里报 [error] avahi_entry_group_add_service_strlst("localhost") failed: Invalid host name,这问题怎么着也没解决。最后换了 ubuntu,顺利异常,所有包都有二进制的,不像在 centos 里还编译了两个包。啥问题没遇到,就进入设置页面了。

今天继续在 centos 上试另一个 phabricator,安装很容易。访问页面报 403 You don't have permission to access / on this server,又搞了良久,chmod 了,chown 了,Allow from all 了,全都没有作用。关键字又按照 error log 里的错误(13)Permission denied: access to / denied 来搜,搜到一个 Fixing Apache (13)Permission denied: access to / 403 Forbidden,按照这个里面一步一步又过了一遍,除了倒数第二步 Make sure that the Directory Above has Execute Permission 没仔细看,倒数第一步也研究了一下,根据究竟什么是SElinux?发现这台 centos 应该不是 SELinux。回过头来仔细读了一下倒数第二步,这里面说 chmod 不能只改 /path/to/webroot/,应该连整个 /path 都改了才行。怀着死马当活马医的心情执行了一下。哈哈哈哈————

Topic: 

Root 安卓时 Superuser app 可能的 bug

昨天 Nexus S 收到 4.1.1 的 OTA,root 后在 Superuser 检查更新时到“正在获取 root 权限”这一步总是失败。

网上搜了半天,有一个人说 Superuser 设置的安全选项里的自动响应,默认是“拒绝”,需要改成“允许”。我看了一下我的设置里这一项就是“允许”,于是我改成“提示”想试一下看看是啥效果。

然后“点击此处检查更新”,过了一会,弹出了提示,手动允许后,一切顺利。DroidWall,GAE 代理之类的终于可以用了。

Topic: 

抱歉,孩子

最近六个星期,爸爸在印度出差,昨天是你一周岁生日,爸爸不在你的身边,甚至连 blog 也没有在当天写出来。好在妈妈已经写了,妈妈这段时间一定比以前更辛苦了。今天是母亲节,祝天下所有的母亲,节日快乐。

但是爸爸真正想抱歉的不是这个。你的生日是如此特殊,以后你懂事了,你会看到每年中国人都会在这一天悼念。爸爸想抱歉的正是与此有关,爸爸没有太多的力量来改变这个国家,让你有一个非常好的成长环境。爸爸在这里对你说一声,对不起,孩子。

让我转载一篇 blog 来讲述那段历史吧。

李承鹏:写在5.12的爱国帖 发布于 2012-05-12 18:53

那年油菜花比往年晚开了整整一个月,人们并没有意识到什么。那时人们还相信专家,专家说花期推迟很正常,青蛙上街很正常。那天我正在书房赶一篇文章,地动时还以为家猫在脚下调皮。直到满书架的书往外飞,才明白是地震。

大楼摇晃、灯杆倾斜、天边发出异光,总之那个景象十分特殊,像末日降临。我拼命冲下楼,地面像煮沸了一样抖动,地面下像有无数双手在抓脚后跟,好容易跟一些邻居逃到小区外空地……慢慢地才知道都江堰死了很多人,北川已封路了,血浆都不够用了。那时我正处于一个爱国青年的尾声,纠结处热情最为猛烈,我认为报效国家的时候到了,要用我们的血肉筑起新的长城。我在头晚到处张罗捐款后,次日清晨与唐建光、郑褚进到北川。

可是,我在北川一中面临着人生很大的一个困扰。我无法解释为什么五层高的新楼倒塌后只有半个篮球场那么大,而几十年前修的旧楼竟没有倒塌。我也无法解释为什么楼房脆得像饼干一样且建渣里面没什么钢筋,连一楼的学生都没来得及逃脱。一个妇人一直在我身边走来走去,她已不太哭得出声,只指着那堆很渺小的建渣:看,那是我娃娃呀,手还在动,她还没死,但是我扯不出来她啊……那个情景令人崩溃,我看得见那个女娃娃碎花衣服的一角,还有其他孩子的衣角,他们中很多还在动,可按部队命令我们不能上前,因为过脆的废墟不能轻易站人,否则会引起二次崩塌。就这样眼看孩子们的身体还在动,与那些石头一起,慢慢变冷,而我们无能为力。

在此之前我还是个爱国青年,我相信生活的很多不幸是敌对势力造成的。我在球评里写“大刀向鬼子头上砍去”,这些总打败中国队的家伙是南京大屠杀的后裔。我骂过CNN长了口蹄疫,因为蒂弗莱说中国人几千年来都是暴民和垃圾。我也不反对抵制家乐福,觉得这一个侧面也可唤醒民主意识。我家离美领馆很近,99年美国导弹轰炸我驻南大使馆时,我也在美领馆外高举过抗议的拳头。同年前往美国采访时,我写过一句“像一枚导弹打进美国本土”,深觉这句子十分有力。

可站在北川学校废墟前,我很困惑。我还坚持过去一些爱国观点,但开始明白建渣里的钢筋并不是帝国主义悄悄抽走的,那些孩子也不是死于侵略者的魔爪,而死于自己人的脏手。我更困惑的是,为什么911死难者都有名字,而我们的孩子没有名字。我认为我们当然要用血肉筑起新的长城,可另一方面,长城也应该要保护我们的血肉。爱国主义应该是双向的,单向收费的不是爱国主义,是向君主效忠。

我从2008发生变化,如果晚年写自传,我会以2008为基点,在此之前我是一个混蛋。那段时间与其他一些志愿者天天在北川山里晃,救了一些老人和小孩,无意发现有一所希望小学远好无损甚至连玻璃窗都没怎么震碎,最后学生们在老师带领下翻过三座大山逃到山外。我问过校长和老师为什么出现这个奇迹,他们说得感谢那个监工。那个监工是捐款企业派来的,工程兵出身,修建过程中天天用小锤子敲水泥柱子听声音,他能从声音里听出有没有多掺沙子,圆石比例、水泥标号是否匹配,如果不合格就责令返工。老师告诉我,那些日子工地上除了施工声音就是这个监工跟人吵架的声音,除因质量问题吵,就因向当地政府追款吵。因为,企业捐助希望小学的款都要先交当地政府掌握,再由政府拨给具体施工单位……最后一架是关于操场的,终于成功追款修起了操场。大地震发生时,正是这个操场庇护了几百名孩子。

我问过这所希望小学是不是用了特殊标准才修得这么坚固。这个监工说:不,只是按国家普通建筑标准修建的。我又得知,这个监工监理了五所学校,在那场大地震中奇迹般地无一垮塌。他说:没什么奇迹,所谓奇迹,就是你修房子时,能在十年之前想到十年之后的事情。

可是他从来不能被主流媒体宣传,名字也一直不能公布,后又传出他所属的企业其实涉黑。前两年的一天晚上,他打来电话,说正在被精神病医生治疗着,老婆也离婚了,他现在想带着女儿逃出四川,问我能不能帮他远离这是非之地,在北方找一个工作……后来我们就断了联系。

我从2008年开始变化,一个人生平第一次看到无数的冤魂,肯定会变化。我持续四年的困惑:我们不仅不能公布那些死去孩子的名字,也不能公布救了很多孩子的监工的名字。今天是汶川大震四周年,这里正式公布他的名字:句艳东。

最近大家很爱谈爱国主义。在我看来,不要狭隘理解爱国主义就是敢于抵御外敌,爱国主义更是敢于抗争内贼,这如同你爱你们村,不仅表现在敢于同别村抢水源时打架,更表现平时勤恳耕种、爱护资源、不对本村妇女耍流氓……一方面欺负本村人民,一方面为了财主利益勇敢跟别村打架,这不叫爱国主义,这叫勇当家丁。所以我认为句艳东是十足的爱国者,他没去攻打钓鱼岛黄岩岛,可他救了很多孩子,他应当得到彰显,可弘扬名望的舞台被骗子占领着,我在灾区一月见闻,多少骗子假太阳光辉之名横行……我们深爱的国家正在逆淘汰、逆宣传、逆袭真相,如果一个国家的爱国主义宣传着一些骗子,这个爱国主义本身就是骗局。

5.13下午再次强烈余震,接命令必须外撤,走了几公里撤到山口时正碰到央视张泉灵在时空连线,无意中我一身雨水的形象被摄进镜头。刚到山下,一个素以厚道著称的央视记者打来电话:你丫真会出风头,没事儿你跑北川干嘛呀,抢我们台镜头。我说:日你妈。绝交至今。一月后回京碰一著名央视仁义大哥。聊起豆腐渣工程,我说:贪官该杀几个。仁义大哥深邃地看着我:不,中国的事情要慢慢来,否则又会乱,毕竟重建还要靠他们呀。又过三年,我不小心批评了倪萍“共和国脊梁”,该名仁义大哥电话里斥:你丫骂人倪大姐干什么呢,她可是好人哪。我在香港书展调侃于丹余秋雨伪善,仁义大哥再斥:想不到这几年你变成这种人,承鹏,咱不能只破坏不建设,不能见政府干的事都是错的。

我曾经如此欣赏仁义大哥,现在大家天各一方,形同陌路。他那些不知是矫造还是表演的关于公平正义的话在微博流传着,星光灿烂,粉丝推崇。以及类似仁义大哥这样的爱国者总说:不管国家有这样或那样的问题,可我们仍要爱这个国。我觉得这是个病句,我爱这个国,可我不能去爱制造豆腐渣工程的政府,更不能去爱给学校修豆腐渣给自己修豪华办公楼的政府官员。

我认为我仍是一个爱国者,可历经2008年的奥运、毒牛奶特别是汶川大地震,我对爱国主义重新定义。爱国主义肯定不是一边说是外人抢劫了我们,一边亲自掠夺国人财富的主义;不是一边说恶邻让我们石油紧缺,一边派出发改委只涨不降的主义;不是一边号召不要让强盗欺负我们的母亲,一边在大地震里让很多的母亲被欺侮的主义,她们看得见自己孩子的手还在动,却无能为力。那天我发了一条很爱国的微博:爱国主义就是,你并不拥有一寸私土,却宣称用生命保卫这片领土。这情形就像你并不在银行里拥有一分存款,却宣布誓死捍卫里面的金库……而且,此时你并不知道劫匪在哪里,银行保安是否把你当成劫匪。

这条微博伤害了很多爱国者的感情,纷纷斥责我为汉奸。我认为这又是个病句,在中国官不至厅局级,财产不过一个亿,每年不去开几个峰会哪好意思夸自己是汉奸。又说我是带路党,可是不拿几张绿卡儿女不开着法拉利在名校上学不在美国置几处房产哪有资格带路。还有说,母亲无论怎样打骂过我们,可毕竟是生我养我的亲妈啊。我就突然想起爱国者曲啸了——尼玛谁见过这么下毒手打骂自己孩子的亲妈?

我其实相当地不反对打黄岩的,可反对只打黄岩不打黄贼。可爱国者逻辑是:打黄贼得给政府一些时间,打黄岩迫不及待。对此我只有一个解析:多少黄贼,假打黄岩之名逃于法网之外。就想起五四运动中的梅思平,假爱国之名火烧曹家,可日本人打来时第一批参加了汪伪政府。

这样比爱国主义胸大肌其实很难证明真伪,说实话这三十年中国实力取得不小进步,至少近期不太可能有大批日本鬼子打进家门,所以那些组织义勇军半夜去炸碉堡的行为基本属于自我催眠的英雄幻想,不如让我们谈谈务实的爱国主义:爱国主义是给孩子修校舍时少一分回扣,多几根钢筋;爱国主义是少修点豪华办公楼,多建些实用农舍;爱国主义是少喝点爱心茅台,多吐槽些醒世真言;爱国主义是少宣传些虚假的英雄,多公布些逝去的名字;爱国主义是能让国民在这个国自由迁徙、念书,而不是平民子弟五证齐全才能就读京城;爱国主义爱的不是国家专政机器,而是去爱一种共同价值观……重要的不是拥护广袤的领土,更重要的是拥有生活的尊严。

小小黄岩,以我军威武几排炮就打成粉齑,收回失地指日可待,以壮国威;重重汶川,多少魂灵在飞,不惩前毖后,君将空负民心。

我是一个爱国者,所以,我在乎庞大的领土多一个小岛的名字,更在乎小小的纪念碑上回归数万亡灵的真实姓名——是为写在5.12的爱国帖。

Topic: 

从王立军想到林彪

@tom2009cn: 你太高估这两人的影响力了,林彪外逃让哪一代人觉醒了?统治集团内部斗争的牺牲品只不过让人们看到他们内部的丑恶,何谈觉醒?王立军就提不起来了,他的角色本来就是一个高级打手,踏进使馆就是一出闹剧。

看见某推油这句话,我倒是有些感叹。林彪有功么?按照公开的资料,对中华人民共和国来说,他确实有功!林彪有过么?按照公开的资料,对中华人民共和国来说,我看不出他有啥过,至少没有足以判死罪的过。但是他死了,死得还挺惨,为什么?想清楚了为什么,才谈得上有那么一点点“觉醒”。

堂堂元帅,没干啥误国误民的事,要弄到狼狈逃窜的境地,这是一个正常国家应该出现的事么?有什么事,不通过法律解决,却要冒着叛国的罪名逃跑,这说明元帅也知道中华人民共和国的法律不保护任何人,这说明元帅也知道中华人民共和国没有真正的法律。没有真正的法律,请问这个国家上至战功彪炳的元帅中至做大买卖的富贵商人下至兢兢业业还月供的升斗小民,有谁有真正的安全感?在这种情况下,所谓的繁荣昌盛有什么意义?

Topic: 

2011年即将过去

这一年最大的变化就是升级当爹了。以往每年生日时都在感叹又老了一岁,到了今年生日那天,完全没有感觉这一天是个特殊的日子。老不老,小不小与我已没有关系,我的视线已不在自己身上。
2012年肯定会发生更多有趣的事情,让我们拭目以待。

Topic: 

微波炉的灯

每次用完微波炉,因为里面有水汽,所以我一般要敞一会门散掉。这个时候微波炉里的灯就那么亮着。如果忘记及时关上微波炉的门,灯亮一两个小时两三个小时也不奇怪。

总是担心总有一天灯会坏掉——看上去这个灯可不好换。终于有一天灯不亮了,突然发现以前的担心都是瞎担心,没灯也不影响啥,坏了,不亮了,心里反而舒坦了——至少不用担心费电的问题了。

微波炉的灯应该设计成一开门就自动熄灭才对嘛。

Topic: 

iPad 和 socks

起因是这样的,前段时间 LD 表弟的 iPad2 托人从美国带过来时经过了我们的手,LD 对这个白色的设备很有些兴趣,后来有一天我在 twitter 上看到有人说 Apple 中国官网上又开卖 iPad2 了,考虑到我家的 kindle3 被 LD 长期霸占,我于是撺掇 LD 咱们也买一个 iPad2 吧。

今天 iPad2 到了,LD 果真扔下了 kindle3 兴冲冲地开始玩这个新玩具,看到有个 youtube 的应用,然后——杯具地撞墙了……

这算是拿到 IT 设备的第一件大事了,google 之。因为平常在电脑上用 ssh turnnel 翻,基于此,iPad 大概有两种方法,一种在网络设置 http 代理的地方填上某个 pac 文件的地址,而在这个 pac 文件里给出 socks 的 ip 和端口。另一种是安装软件,把 socks 代理转成 http 代理(这篇 blog 的留言值得一读,有不少信息)。

对于我来说,似乎直接用 pac 文件更方便一点。不过上面那个文章里面的 pac 文件示例太简单,任何网址都去走 socks 代理。而实际上对于国内网站也用代理既不经济也不实惠,甚至今天发现优酷的某些视频只允许大陆地区的 ip 观看。那么 pac 文件里简单的过滤需要一点。如果不想使用正则的话,可以参看一下我下面写的。除了过滤被墙的网址外,主要目的是处理子域名不需过滤的情况。比方说对 google.com 使用代理,但是 mail.google.com 却不需要。

  1. var patterns = new Array("twitter.com", "twimg.com", "youtube.com", "ytimg.com", "google.com");
  2.  
  3. var patterns_black = new Array("mail.google.com");
  4.  
  5. function FindProxyForURL(url, host) {
  6.     for (i in patterns_black) {
  7.         if (url.indexOf(patterns_black[i]) >= 0) {
  8.             return "DIRECT";
  9.         }  
  10.     }
  11.     for (i in patterns) {
  12.         if (url.indexOf(patterns[i]) >= 0) {
  13.             return "SOCKS 192.168.X.XXX:YYYY";
  14.         }
  15.     }
  16.     return "DIRECT";
  17. }
订阅 RSS - qyt的博客