Android中的Settings分析
Chm格式解析
现在客户又要求支持CHM格式的电子书,没办法又要研究CHM。
CHM格式文件主要由3个部分组成,CHM初始化头、CHM头节点、内容。
The Header(CHM初始化头)
CHM初始化头部分主要提供CHM文件的基本信息、头节点信息等
0000:char[4] TISF
0004:DWORD 版本号
0008:DWORD 文件头的总长度,包括后面的节点表,v3版本是0x60,v2是0x58
000c:DWORD 1
0010:DWORD 时间戳
0014:DWORD windows语言ID
0018:GUID {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC}
0028:GUID {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC}
NOTE:GUID的字节顺序,判断时候容易搞错。
后面跟的是2个节点表,每个节点表的长度是16个字节。
0000:QWORD 节点的offset 0008:QWORD 节点的长度
V3版本在节点表后面还有8个字节
0000:QWORD 内容的偏移量
The Header Sections(头节点)
Header Section 0 (节点0)
节点0主要包含文件大小信息,还有一些其他的信息
0000: DWORD $01FE (unknown) 0004: DWORD 0 (unknown) 0008: QWORD File Size 0010: DWORD 0 (unknown) 0014: DWORD 0 (unknown)
Header Section 1:The Directory Listing(目录列表)
这是整个CHM文件最重要的部分,它包括了文件的目录和信息
Directory Header
目录列表最开始是目录头信息,它的格式如下:
0000: char[4] 'ITSP'
0004: DWORD Version number 1
0008: DWORD Length of the directory header
000C: DWORD $0a (unknown)
0010: DWORD $1000 Directory chunk size
0014: DWORD "Density" of quickref section, usually 2.
0018: DWORD Depth of the index tree 1 there is no index, 2 if there is one level of PMGIchunks.
001C: DWORD Chunk number of root index chunk, -1 if there is none(虽然有的文件没有index chunk,但是这里不是-1而是0,大概是个bug)
0020: DWORD Chunk number of first PMGL (listing) chunk
0024: DWORD Chunk number of last PMGL (listing) chunk
0028: DWORD -1 (unknown)
002C: DWORD Number of directory chunks (total)
0030: DWORD Windows language ID
0034: GUID {5D02926A-212E-11D0-9DF9-00A0C922E6EC}
0044: DWORD $54 (This is the length again)
0048: DWORD -1 (unknown)
004C: DWORD -1 (unknown)
0050: DWORD -1 (unknown)
The Listing Chunks
目录头后面紧跟的就是目录块,有2种目录块,一种是Listing chunks,一种是Index chunks。如果chm文件只有一个Listing chunk,那么Index chunk就被省略掉。每个Listing chunk的结构如下:
0000: char[4] 'PMGL' 0004: DWORD Length of free space and/or quickref area at end of directory chunk(一般chunk的大小是固定的,所以后面还有一些填充和用于快速查找条目的数据) 0008: DWORD Always 0. 000C: DWORD Chunk number of previous listing chunk when readingdirectory in sequence (-1 if this is the first listing chunk) 0010: DWORD Chunk number of next listing chunk when readingdirectory in sequence (-1 if this is the last listing chunk)
0014: Directory listing entries (to quickref area) Sorted by filename; the sort is case-insensitive.(从0×14开始,就是按照文件名顺序排序的目录条目)
Listing chunk最后的是快速定位区域,其中密度的计算方法是n = 1+(1<<Density),这个Density就是Dircetroy Heaer中Density区域的值。
从chunk尾开始读取的数据顺序为: Chunklen-0002: WORD Number of entries in the chunk Chunklen-0004: WORD Offset of entry n from entry 0 Chunklen-0006: WORD Offset of entry 2n from entry 0 Chunklen-0008: WORD Offset of entry 3n from entry 0
以此类推
总的个数就是 (entry的总个数)/密度n。
Directory listing entry的结构如下: ENCINT: length of name BYTEs: name (UTF-8 encoded) ENCINT: content section ENCINT: offset ENCINT: length
其中偏移量是从解压缩之后的正文段的开始来计算的,同样长度也是表示解压缩之后的长度。
在目录中存在两种文件,用户数据文件和格式信息文件,格式信息文件以两个连续的冒号“::”开头,用户数据文件以“/”开头。
The Index Chunk(索引块)
索引块的格式如下:
0000: char[4] 'PMGI' 0004: DWORD Length of quickref/free area at end of directory chunk 0008: Directory index entries (to quickref/free area)
quickref的格式和排列与列表块中相同。
每一个目录索引项的结构如下:
ENCINT: length of name BYTEs: name (UTF-8 encoded) ENCINT: directory listing chunk which starts with name
当有索引块的层次较多时,将不再存储数据块号而是存储下一层的索引号。
解释一下encint型变量的编码规则:
一种可变长度的整型变量,第一个字节只使用低7位,最高位为1表示该字节之后的下一字节的低7位要接在这7位的尾部组成一个数,这样通过移位相加的运算,直到遇到最高位为0的字节,可以组和成一个长度可调节的整数。
The Content(正文)
在版本3中,正文一般紧跟着文件头,而且在文件头表之后有一个双字用来指定其位置。在版本2中,正文部分紧跟着文件头。所有第0段正文内容在目录中的位置都是相对那点来说的,其它的正文段都在content section 0中。
The Namelist file(名称列表文件)
名称列表文件是在content section 0中,并且在目录块中的文件名为”::DataSpace/NameList”。它其中包含着所有正文段的名称,其格式如下:
0000:WORD 以字(2个字节)计数的文件长度 0002:WORD 文件中的entry数 对于每一个entry格式为: 0000:WORD 以字(2个字节)计数的名字长度,不包括最后的NULL结尾符 0002:WORD 以word 0表示所有entry的结束。 xxxx:WORD 字0
名称的编码类似于UFT-16,小端存放,是以0为结尾。
段的名称目前为止只有两种,Uncompressed和MSCompressed。
Uncompressed段表示自解释文件,一般都没有压缩。
MSCompressed段表示Microsoft LZX压缩算法压缩的文件,这个段是主要的内容。
The Section Data(段数据)
对于段号不为0的段,还有一个文件为::DataSpace/Storage/
/Content,里面存放着该段的压缩信息,所以,当解析
非0段时,需要两步工作,第一步,通过段0的The Namelist file取得段名,才能利用段名找到相应的段,第二步,就是解压段信息,获取到想得到的数据。
Other section format-related files(其余与格式相关的文件)
还有一些与段有关的文件。
::DataSpace/Storage//ControlData
共0×20个字节,存储关于压缩的信息
0000:DWORD 在“LZXC”串后的双字个数,在版本2中,此值必为6 0004:DWORD ASCII第二个双字为“LZXC” 0008:DWORD 版本信息,必须大于2 000C:DWORD 为LZX reset interval 0010:DWORD 为窗口大小. 0014:DWORD 为缓存大小 0018:DWORD 0,未知信息。 001C:DWORD 0,未知信息。
其中在版本1中,reset interval、窗口大小、缓存大小是以字节计数的,在版本2中,是以8000字节为单位计数的。
::DataSpace/Storage//SpanInfo)
由一个QW存放着未解压的段的长度信息。
::DataSpace/Storage//Transform/List
本来是存放GUID列表用于解压缩段数据的,但是只存了每个GUID的一半。似乎是存储GUID字符串的长度是以字符算的,实际存储是以unicode双字来存的。
Appendix: The Compression(压缩说明)
压缩段一般都是用LZX压缩,这个压缩算法是微软经常用的。要确定是否是使用这种压缩方式,可以先读取先要读ControlData文件(就是上面讲的)。要解压数据首先要读取::DataSpace/Storage//Transform/
{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable文件,其格式如下:
0000:DWORD 版本信息 0004:DWORD reset table中的entry数 0008:DWORD 8,每一个entry的大小 000C:DWORD 表头长度 0010:QWORD 压缩前长度 0018:QWORD 压缩后长度 0020:QWORD 块大小,一般是0x8000 0028:QWORD 第0个压缩块在内容中的偏移 0030:QWORD 第一个压缩数据块边界在内容中偏移 ...以此内推
CHM文件解析流程和显示设计
通过上面的讲述,我们已经知道了CHM格式的解析,现在我们可以开始进行解析的代码设计了。
- 从文件0偏移开始解析The Header信息,获取The Header Sections位置信息。
- 解析Header Sections头信息,得到The Listing Chunks信息和The Index Chunk信息。
- 通过The Listing Chunks获取到Directory listing entry信息。
- 首先获取到::DataSpace/NameList信息,得到content sections的名字。一般都是Uncompressed和MSCompressed两个content sections。
- 通过::DataSpace/Storage/<SectionName>/SpanInfo、::DataSpace/Storage/<SectionName>/ControlData和::DataSpace/Storage/<SectionName>/Transform/List得到content section的相关信息。
- 通过the listing chunk得到.hhc文件,这个文件是chm格式的TOC文件,可以得到目录信息,通过目录信息,得到各个文件名,再通过文件名和the listing chunk就可以得到各个文件在content section的位置。
- 通过文件在content section中的偏移、reset_lzx_table和LZX control的信息,解压缩conten section得到文件的真正数据。
- 解压缩后得到的信息是HTML格式的,所以最后还要解码HTML信息,显示文件。对了,其中还有一些css文件,这些文件对应着HTML文件的排版,这些也是需要解压缩得到的。
最后还是代码实现了,计划找个时间做个android版的CHM阅读器。
编程语言杂谈
作为一个IT人,编程语言就是我们赚钱的工作,打仗的武器。工作了一段时间了,有一些想法,近期看到<<2012年3月编程语言排行榜>>,想到自己以后的发展,发表一下自己的想法。
现在编程语言很多,如果每种都要学,估计一辈子都学不完,我挑选了几种觉得自己有必要学的几种。
1、C/C++ 学校里面就学了,不敢说精通,熟练还是可以的。C语言应该是编程语言的基础把,个人觉得这个是必学的。
2、java 如果想做跨平台的应用,这个还是很有必要学习的,主要还是面向对象的思想,java会有进一步的理解。
3、javascript/hmtl/css html不是编程语言,但是可以将这3个可以作为一类把,都是做网页设计用的,随着HTML5越来越火爆,这3种语言的以后的火爆程度可想而知,特别是javascript,以后的HTML5网页应用少不了把。
4、php 还没有研究过,不过以后想对网络服务器的运作有了解,这个也是要稍微懂点。
5、sql 好像越来越火爆,可能是因为用的越来越广,这个也要了解,稍微懂点。
6、Ruby 这个现在接触的比较少,但是如果一种语言被描述成巧妙、优雅且易用更实用有趣的语言,这引起了我很大的兴趣。如果真如上面所说的,我希望ruby能有很好的发展,至少能帮IT男减少一点苦逼把。
Objective-C个人不推荐学习,如果想近期学这个做IOS应用赚点外快的话,可以学。如果现在只是兴趣的话,真的不推荐。因为它现在的应用领域太封闭了,有点时间还不如学学其他的。
说了这么多,发现要学的东西还有很多,希望自己坚持下来。
活到老,学到老。
ubuntu下安装google输入法
在Ubuntu 10.04下安装Google拼音输入法,如下:
A. 获取代码:(没有git的先安装git:sudo apt-get install git-core)
$ git clone git://github.com/tchaikov/scim-googlepinyin.git
$ cd scim-googlepinyin
B. 编译前提:
上面给的链接里面有介绍怎么编译的,但少提了几个必需组件,这里列一下:
* autotools-dev
* libgtk2.0-dev
* libscim-dev
* libtool
* automake
用下面命令看看是不是安装了,如果没有,会自动帮你安装上:
$ sudo apt-get install autotools-dev libgtk2.0-dev libscim-dev libtool automake
C. 编译:
记住系统必须先存在SCIM(没有的话 sudo apt-get install scim 一下)
$ ./autogen.sh
$ make
$ sudo make install
现在重启scim:
pkill scim
scim -d
去首选项里的“语言支持”的“输入法”选择scim作为默认输入法,然后注销或者重启电脑就OK了。
ubuntu scim google pinyin无法跟随光标
sudo gedit /etc/X11/xinit/xinput.d/scim
将GTK IM MOUDLE=xim
QT IM MOUDLE=xim
后边的xim改为scim,保存退出。
设置scim输入法:
系统->首选项->SCIM输入法设置->Global Setup->将“Embed Preedit String into client widnow”前面的勾去掉。
系统->首选项->SCIM输入法设置->GTK->将“Embedded lookup table”前面的勾去掉。
重启scim:
打开终端,输入pkill scim
然后输入 scim -d
Ubuntu下获取webkit的源码
1.首先安装svn:
sudo apt-get install subversion
2.checkout代码:
svn checkout http://svn.webkit.org/repository/webkit/trunk WebKit
我在checkout webkit代码的时候出现下面的问题:
svn: PROPFIND of ’/repository/webkit/!svn/bc/19963/trunk/LayoutTests/fast/xpath/4XPath/Core/test.js’:207 Multi-Status (http://svn.webkit.org)
解决办法:
1. 删除LayoutTests/fast/xpath/4XPath/Core这个目录
2. 用svn单独checkout http://svn.webkit.org/repository/webkit/trunk/LayoutTests/fast/xpath/4XPath/Core
3. 然后将这个core目录拷贝到LayoutTests/fast/xpath/4XPath/ 下面
4. 更新webkit
Android AsyncTask理解和使用
这两天在研究一个在线音乐的android客户端,其中比较多的就是AsyncTask的使用了。在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。在单线程模型中始终要记住两条法则:
1. 不要阻塞UI线程
2. 确保只在UI线程中访问Android UI工具包
而客户端通过网络去服务器获取信息的过程一般是比较长并且不固定的,因此,网络相关的操作肯定是不能在UI线程进行的。为了解决这个问题,android提供了一个类:AsyncTask.它使创建需要与用户界面交互的长时间运行的任务变得更简单。相对来说AsyncTask更轻量级一些,适用于简单的异步处理,不需要借助线程和Handler即可实现。
AsyncTask是抽象类。AsyncTask的结构图如下:
AsyncTask定义了三种泛型类型 Params,Progress和Result。
Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。
子类必须实现抽象方法doInBackground(Params… p) ,在此方法中实现任务的执行工作,比如连接网络获取数据等。通常还应该实现onPostExecute(Result r)方法,因为应用程序关心的结果在此方法中返回。需要注意的是AsyncTask一定要在主线程中创建实例。
AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,需要注意的是这些方法不应该由应用程序调用,开发者需要做的就是实现这些方法。在任务的执行过程中,这些方法被自动调用,运行过程,如下图所示:
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI thread中创建
2) execute方法必须在UI thread中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
4) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。
下面是一个具体的使用实例。
private class NewsTask extends AsyncTask<Void, WSError, Album[]> {
@Override
public void onPreExecute() {
mViewFlipper.setDisplayedChild(0);
mProgressBar.setText(R.string.loading_news);
super.onPreExecute();
}
@Override
public Album[] doInBackground(Void... params) {
JamendoGet2Api server = new JamendoGet2ApiImpl();
Album[] albums = null;
try {
albums = server.getPopularAlbumsWeek();
} catch (JSONException e) {
e.printStackTrace();
} catch (WSError e){
publishProgress(e);
}
return albums;
}
@Override
public void onPostExecute(Album[] albums) {
if(albums != null && albums.length > 0){
mViewFlipper.setDisplayedChild(1);
ImageAdapter albumsAdapter = new ImageAdapter(HomeActivity.this);
albumsAdapter.setList(albums);
mGallery.setAdapter(albumsAdapter);
mGallery.setOnItemClickListener(mGalleryListener);
mGallery.setSelection(albums.length/2, true); // animate to center
} else {
mViewFlipper.setDisplayedChild(2);
mFailureBar.setOnRetryListener(new OnClickListener(){
@Override
public void onClick(View v) {
new NewsTask().execute((Void)null);
}
});
mFailureBar.setText(R.string.connection_fail);
}
super.onPostExecute(albums);
}
@Override
protected void onProgressUpdate(WSError... values) {
Toast.makeText(HomeActivity.this, values[0].getMessage(), Toast.LENGTH_LONG).show();
super.onProgressUpdate(values);
}
}
两根木棍
分享一个简短的日记,于2006年3月20日 20时9分想到我初恋女友的时候写的。这个日记用小说的形式记录了以前发生在我身上的事情,故事是真实的,情节有些虚构。再过不久,故事中的女主人公就要结婚了,新郎当然不是我,而我又感慨万千,重发此文以祝福他们的婚姻幸福美满:
她的双手放在后面神秘地对我微笑。“神经婆” — 我这样想。她似乎听见了我在骂她,辩解地说:
“没什么,呵呵。”
“哦?”
“只是想问你一个问题拉。”
“快说咯,你这样看我,我不疯掉都有鬼。”
她笑着,头略侧,笃地转回来,既而又以她最快的速度往我身上仍了几个字过来:
“恩,你会喜欢我多久?”
“我怎么知道。”我条件反射似的以更快的速度仍回了这几个字。好象事先我已经排练过无数次或者这是上帝先天赐予我的必杀技吧。而且,她好象真的中了我的必杀技,先是呆了下,突然又大笑起来:
“没有拉,只是我今天在书上看到了一则很有趣的东东。”
说着拿出了她手中的东西给我看。两根木棍,一长一短。我一边像小孩子一样呆望着她听她讲她的东东,一边想:女生怎么都这样白痴,连书上的东西都相信。以致让我长时间觉得,男生爱装白痴,而女生是真的白痴掉了。不过现在想起来,还是很可爱的,一点都不白痴。或许,白痴就是可爱吧。
“我把尾巴藏起来,不让你知道哪个长哪个短,然后你抽一根。”
“干嘛啊 。骗小朋友的把戏啊?鬼才干饿。”
“才没有拉,哎呀,你就先抽一根看看,要不我生气不理你了。”
“哦哦,我抽就是了。这根…恩…还是这根好…诶,不对……” 我的手指来指去,不知道抽哪根好。当时真怕那丫头搞恶作剧把我骗了。
“速度,快啊!”她催我了。
“好,就这根了。”我伸手去抽,却发现要抽的那根被她抓得有点紧。我反应速度:
“算了,还是这根了。”
“哈。我就觉得嘛,你这白痴,除了我还会有谁理你啊!恩,这里有两根,长的呢,就代表你会喜欢我很久;短的呢,就说明你是花心大萝卜。”很明显,我抽到长的了。听她一长串的话,我显得有点愕然:
“真的假的?”
“恩,星星作证。”
在我的记忆中,我看着那星星发了好长一段时间的呆,之后的画面已经模糊地记不清了。从那次发呆到现在已经过了很长时间,事实上,结果是这样的:我喜欢了她近两年的时间。
可我对这件事一直很困惑,不知道那次抽的木棍到底准不准。因为“很长”是很模糊的概念,是没有明确的界限的。两年的时间算是长久还是瞬间,谁能给我这个答案?可毕竟,那是星星作证的呀,应该算是很准的吧。哦,两年算是很长的时间。星星给我了答案。
可我不明白的是,那短木棍为何更紧呢?每每想到这里,想到那早已遗忘的木棍、模糊的星空以及那消失的单纯的想法,我的心总是隐隐有些疼痛。我似乎更愿意相信,从那到现在只是瞬间。确实,只要你站在现在的角度去看以前的事,有什么不是瞬间的呢?想来,到我快死的时候,看看现在的我,已然凝固,成了瞬间。既然如此,何必追求什么结果,享受过美丽的过程,即使是短暂、瞬间的美丽,不也该知足了么?
全文完。
如今,那些星星与木棍早已远去,之前稚嫩的纪念文字在如今看来,还是让我有些许感动。这些感动让我觉得开心,原来我也不曾错过什么,不曾错过那个年龄最单纯美好的东西。而现在正在经历的,也是这个年龄该经历的。一个人从来不会错过什么,所有经历都值得我们珍惜和记忆。

