Life

Beancount —— 命令行复式簿记

本文介绍复式簿记的基本概念以及如何使用 Beancount 记账。本文适合的读者:

  • 想要记账的;
  • 曾经或正在记账但是目前对记账方式/软件不满意的;
  • 控制欲强的。

一、为什么要记账

记账能让自己了解自己的财务状况,用大白话来说就是能回答以下问题:

  • 我的钱从哪来?
  • 我的钱在哪?
  • 我的钱去哪了?

一本维护良好的账本能生成很多有用的财务报表,其中最有用的是「损益表」和「资产负债表」,前者能回答第一个和第三个问题,后者能回答第二个问题。为了维护一本良好的账本,你需要科学的记账方法和科学的记账软件,本文将向你安利一种科学的记账方法(复式簿记)和一套科学的记账软件(Beancount)。阅读以下内容之前,你需要做好以下准备:

  • 有基础的会计知识,至少听说过「会计恒等式」;
  • 能熟练地在终端里编辑文本文件,无论 Vim 或是 Emacs;
  • 对自己的财务状况有基本了解,并愿意对此做出优化。

有以下技能会更方便:

  • 基础的 Python 知识,或是其他适合于文本处理的编程语言知识(用于导入银行账单);
  • 熟练使用 Git 等版本管理工具(用于跨设备同步)。

二、什么是复式簿记

复式簿记是一种把每笔交易都记录到复数个账户中的簿记方法。举个例子,想像你面前有两个桶,分别是「资产」(Assets)和「费用」(Expenses),左边的桶里装满了豆子,右边的桶是空的。你用 1000 元听了一场演唱会,为了记录这笔费用,你把 1000 粒豆子从 Assets 桶里转移到了 Expenses 桶里,代表你的资产减少了 1000 元,而你花在演唱会上的费用增加了 1000 元,在这个过程中,豆子的总量没有变化(资产减少的豆子与费用增加的豆子数量一致),这便是最简单的复式簿记。

实际上,复式簿记系统中,一般有五个大桶,每个桶里又可以放很多个小桶,这五个大桶分别是:

  • 资产 Assets —— 现金、银行存款、有价证券等;
  • 负债 Liabilities —— 信用卡、房贷、车贷等;
  • 收入 Income —— 工资、奖金等;
  • 费用 Expenses —— 外出就餐、购物、旅行等;
  • 权益 Equity —— 用于「存放」某个时间段开始前已有的豆子,下文详述。

豆子(或是货币)在这五个桶里倒来倒去,出入相抵,这便是会计恒等式。这些桶里剩余的货币数量,则是生成损益表和资产负债表的重要依据。

与传统的复式簿记不同,Beancount 及其前辈们用的复式簿记方法使用了正负号而不是拗口的「借」(debit)和「贷」(credit)来表示五个桶之间的豆子变动,更加容易理解和思考,也不容易出错。本文所介绍的复式簿记采用 Beancount 的方案。

虽然复式簿记可以用来记任何东西的变动,但主要还是用来记货币的变动,因此桶中的数字是可以为负数的。

具体怎么记呢?再举几个例子:

  • 收入→资产:小明是个无业游民,有天他在路上捡到 100 元,没有交给警察叔叔——收入桶倒出 100 元,资产桶增加 100 元;
  • 负债→资产:小明看中一件新衣服,但是买不起,于是问大明借了 200 元——负债桶倒出 200 元,资产桶增加 200 元;
  • 资产→费用:小明用 300 元买了一件衣服——资产桶倒出 300 元,费用桶增加 300 元;
  • 费用→资产:小明发现衣服不合适,要退货,老板说你穿了好几天了,只能退你 250 元——费用桶倒出 250 元,资产桶增加 250 元;

小明完成了这四笔交易之后,四个桶的状态:

  • 收入:-100 元
  • 负债:-200 元
  • 资产:250 元
  • 费用:50 元

这四个桶里的数字加起来是——0 元。因为这四个桶里的数字之和一开始就是 0,而每笔交易都是在桶之间加减,负数和正数的绝对值相等(和为 0),因此总量并没有变化。每笔交易在不同账户的数字加起来和为 0 是复式簿记的重要特性和原则,也是用来检验账目正确性的重要依据。复式簿记这一特性在企业账目管理中有着重要的意义——不同账户的交易内容记在不同的账本上,由不同的财务人员管理,使账目之间互相制约、不容易出错(无论是有意的还是无意的)。

在上面的例子中,「负债 -200 元」和「资产 250 元」挺容易理解的,但是「收入 -100 元」和「费用 50 元」可能不是那么容易一下子想通。如果上面倒豆子的想像没能让你信服的话,以下两个方案有助于理解(但可能并没有倒豆子那么欢乐):

  • 把收入(Income)想像成一个装着你一生(过去和未来)所有劳动成果的桶,每次你的收入都是从桶里取出东西(通常以货币的形式),一直取啊取啊,直到某一天……所以收入桶的数字通常是负数
  • 把费用(Expenses)想像成一个装着你一生(过去和未来)所有消费的桶,每次你的支出都是往桶里放东西(以货币的形式表现),和朋友出去唱歌转换成快乐存进去,看过的电子书转换成精神食粮存进去,吃过的饭转换成……所以费用桶的数字通常是正数

一旦接受了「收入和负债通常为负数」、「资产和费用通常为正数」这两个设定,那你便很容易理解这条等式了:

(Income + Liabilities) + (Assets + Expenses) + Equity = 0

用大白话来说就是:你赚的钱(Income),加上你借来的钱(Liabilities),最终要么变成你自己的钱(Assets),要么就是花掉了(Expenses),最终得到的是个零。这就是人的一辈子……

等下,Equity 是怎么来的?仔细想想小明的例子,他的四个桶要满足这个等式,前提是桶里都是空的。但是小明不是一个刚出生的婴儿,他已经活了二十多年了,之前的 Income、Liabilities、Assets、Expenses 怎么算呢?答案就是放到 Equity 里。当小明决定开始用复式簿记的时候,他从 Equity 里倒一些豆子其他桶里(或从其他桶倒一些豆子到 Equity 里),将其他桶的数字调节成符合当前实际情况即可。实际操作中,人们一般只关心 Income 和 Expenses 桶的数字在某段时间内的变化,并不关心它的总数(除非你想统计你出生到现在一共收入多少、支出多少),只要把 Assets 和 Liabilities 调节准就行了。这便是 Equity 的作用——存放已有的「权益」。

更一般地,Equity 可以用来存放所选取的时间范围之前的「汇总」。比如小明从 2012 年开始用 Beancount,一直用到 2016 年,他想只看 2016 年的财务状况,那 Beancount 便会把他 2016 之前四年的数据「调节」到 Equity 里,来维持 2016 年会计恒等式的平衡。

三、Ledger-like 和 Beancount

Beancount 是一个 Ledger-like 软件。Ledger 是这一类复式簿记软件的开创者。他们共有的特点是:

  • 采用改进的复式簿记方案(使用正负号而不是「借」和「贷」来表示账户之间的变化);
  • 使用纯文本文件作为账本,用户用文本编辑器即可记账;
  • 账本既是用户输入的文件,同时也是软件的「数据库」;
  • 软件读取账本并生成报表,账本本身也可供人类直接阅读。

市面上的复式簿记软件不少(如 GnuCash),但是大部分都是提供一个 GUI,用户在一堆文本框里输入各种数字和文字,软件接受输入然后存储到自己的数据库里(SQLite、MySQL 等)。用户无法直接看到或操作他们的数据,必须通过软件来操作;一旦软件停止更新,用户的数据就危在旦夕:难以导出,难以复用,很难跨平台或跨设备同步。

而 Ledger-like 软件则直接使用文本文件作为账本,用户直接用最喜爱的编辑器打开账本即可记账。软件只是读取你的账本并生成报表,即使软件停止更新,用户依然可以直接阅读账本。你可以方便地在各在平台上记账,甚至跨设备问题也可以用 Dropbox 等同步工具,或是 Git 等版本管理工具轻松解决。

Beancount 是 Ledger-like 软件中优秀的一员,相比用 C++ 写成的 Ledger,用 Python 写成的 Beancount 更轻便,更方便增加插件和二次开发,也增加了很多功能,如灵活强大的多「货币」支持。这里为加上引号是因为,Beancount 其实并不知道什么是「货币」,它记录的只是「通货」(commodity)的变化,所有的 commodity 皆由用户自己定义,因此 Beancount 可以用来记录包括货币在内任何东西的变化,比如年假天数、股票、航空里程、信用卡积分,当然了,还可以用来数豆子。这也是 Beancount 名字的来源。

四、Beancount 基础

Beancount 是个 Python 软件,可以从 PyPI 安装。建议同时安装另一个相关软件 Fava,是 Beancount 的一个漂亮的 Web UI:

virtualenv BEANCOUNT
source BEANCOUNT/bin/active
pip install beancount
pip install beancount-fava

装好之后便可以开始写你的第一个账本了。怎么写?Beancount 作者写了非常非常详细的文档:

比如小明如果用 Beancount 的话,他的账本将是这样的:

1970-01-01 open Income:Windfall
1970-01-01 open Assets:Cash
1970-01-01 open Liabilities:Da-Ming
1970-01-01 open Expenses:Clothing

2016-01-01 * "捡到钱了"
  Income:Windfall                            -100.00 CNY
  Assets:Cash                                +100.00 CNY

2016-01-01 * "向大明借钱"
  Liabilities:Da-Ming                        -200.00 CNY
  Assets:Cash                                +200.00 CNY

2016-01-01 * "XX 百货商店" "买衣服"
  Assets:Cash                                -300.00 CNY
  Expenses:Clothing                          +300.00 CNY

2016-01-02 * "XX 百货商店" "退衣服"
  Expenses:Clothing                          -250.00 CNY
  Assets:Cash                                +250.00 CNY

首先小明需要设立账户。开户日期可随自己喜好定,只需比最早一笔涉及到该账户的交易更早即可。这里小明都使用了 1970 年 1 月 1 日作为开户日期,保证今后记录的各种交易都会发生在这个日期之后。

然后便可以真正开始记了,交易的格式如上所示。其中日期后面的星号(*)代表这是一笔已确认的交易,如果换成感叹号(!)的话,则代表这笔交易有疑惑,后期对账时应注意。对账标志后面则是跟着收款人(Payee)和备注(Narration),需要用引号包起来。Payee 是可选的,只有一个字符串的话,这串字符就是 Narration 了。

小明的账本已经写完了,手工书写,也能肉眼阅读。那么 Beancount 有什么用?当然是生成报表:

(BEANCOUNT) bean-report xiaoming.bean balances
Assets:Cash               250.00 CNY
Equity              
Expenses:Clothing          50.00 CNY
Income:Windfall          -100.00 CNY
Liabilities:Da-Ming      -200.00 CNY

于是小明对自己的财务状况一目了然:有 250 元现金,在衣服上花了 50 元,一共收入了 100 元,欠着大明 200 元。bean-report 没有报错,说明账是平的(总和为 0)。bean-report 还能生成很多报表,使用 bean-report -h 查看帮助。

Beancount 自带了一个朴素的 Web UI,能以交互式的方式查看各种财务报表,执行 bean-web xiaoming.bean 命令,然后在浏览器中打开 http://localhost:8080/ 即可:

小明的资产负债表

小明的资产负债表

小明的损益表

小明的损益表

如果你之前安装了 Fava,还可以用 Fava 看到一个更华丽的 Web UI,执行 fava xiaoming.bean 命令,然后在浏览器中打开 http://localhost:5000/ 即可。由于小明的数据还比较单薄,这里贴两张 Fava 作者的示例图:

Fava 展现的资产负债表

Fava 展现的资产负债表

Fava 展现的损益表

Fava 展现的损益表

在资产负债表(balance sheet)里,你可以一目了然地看到自己有多少资产、资产分别在哪些账户里、有多少负债、是对哪些银行的负债。

在损益表(income statement)里,你可以一目了然地看到自己的每月有哪些收入、收入来自于哪些地方、有多少支出、支出花在了什么地方。

在这些页面里还有更多报表等待着你去探索。

五、Beancount 进阶

以下举几个例子,展现一下 Beancount 和复式簿记能处理多么复杂的交易。这些复杂的交易用单式簿记来记录是困难而极易出错的,但是在复式簿记里却是自然而流畅的。之前说过,复式簿记的「复」是指一笔交易会涉及到复数个账户。小明的例子都是两个账户间「一对一」交易,如一个 Income 账户一个 Assets 账户,或一个 Assets 账户一个 Expenses 账户等。但实际上,生活中会遇到各种「一对多」或「多对一」或「多对多」的交易:购买大件物品时因银行支付限额而使用多张银行卡合并付款;朋友出去唱歌、聚餐每人付的钱不同,事后 AA 平摊等。以下几个例子是有意构造的涉及到两个以上账户的交易,让我们一起来看看小红的账本。

第一个例子

2016-01-31 * "工资 2016-01"
  Income:SomeCompany:Salary                -20000.00 CNY ; 应发工资
  Income:SomeCompany:Reimbursement          -1000.00 CNY ; 餐补
  Income:SomeCompany:Reimbursement           +100.00 CNY ; 餐补扣除
  Expenses:Government:Pension               +1500.00 CNY ; 养老保险
  Expenses:Government:Unemployment           +100.00 CNY ; 失业保险
  Expenses:Government:MedicalCare            +500.00 CNY ; 医疗保险
  Expenses:Government:HousingFund           +3000.00 CNY ; 住房公积金
  Expenses:Government:IncomeTax             +3000.00 CNY ; 个人所得税
  Assets:CMB:C1234                         +12800.00 CNY ; 实发工资

这个例子展现了如何在 Beancount 里体现工资条上的内容。每个月的工资条上总会有各种各样的名目。小红在使用了 Beancount 之后,可以方便地把工资、餐补、三险一金、个税等信息都记录进去,以后能很方便地统计每个月有多少工资是喂狗的。

本例子中有 Income:* 账户有三条记录,Expenses:* 账户有五条记录,Assets:* 账户有一条记录,共八条记录,总和为 0。

第二个例子

2016-02-01 * "XX 购物中心" "购物"
  Liabilities:CMB:CreditCards               -1000.00 CNY ; 信用卡刷卡
  Expenses:Clothing:Pants                    +200.00 CNY ; 长裤一条
  Expenses:Clothing:Shirts                   +200.00 CNY ; 衬衫一条
  Assets:Receivables:Xiao-Mei                +600.00 CNY ; 帮室友小美付钱

小红拿到工资第二天就和小美去购物中心逛街,买了一件衣服一条裤子,花了 400 元,小美没带卡,身上现金不够,于是让小红帮她付钱,以后再还她,于是小红把 1000 元的东西一起刷了信用卡。

本例子中小红的 Liabilities 桶里倒出了 1000 元,往两个 Expenses 桶里各倒了 200 元进去,又往 Assets 桶里倒了 600 元。帮小美付了钱,算是小美欠小红的钱,所以算作资产。所有数字加起来和为 0。

第三个例子

2016-02-05 * "XX 黑心饭店" "和小美吃饭"
  Assets:Cash:Wallet                         -300.00 CNY ; 钱包现金
  Assets:Receivables:Xiao-Mei                -200.00 CNY ; 小美帮我付的现金
  Expenses:Food:DiningOut                    +250.00 CNY ; AA 我的一半
  Assets:Receivables:Xiao-Mei                +250.00 CNY ; AA 她的一半

过了几天小红和小美去一家饭店吃饭。本以为人均消费 100 元左右就可以搞定,没想到了这是家黑心饭店,老板说两人共消费了 500 元,还只能付现金,不能刷卡。小红和小美掏空了钱包,总算凑齐了 500 元现金,其中小红付了 300 元,小美付了 200 元。这顿饭两人还是打算 AA 平分掉。

本例中,倒豆子的桶有两个,分别是代表「小红的钱包」的桶,和代表「小美欠小红的钱」的桶,豆子倒去哪儿了?一半进了「小红的消费」桶,另一半回到了「小美欠小红的钱」桶。整个交易中,数字的总和依然为 0。

第四个例子

2016-02-10 * "在免税店买东西"
 Assets:Cash                                 -200.00 USD
 Liabilities:CMB:CreditCards                 -650.00 CNY @@ 100.00 USD
 Expenses:Clothing:Pants                     +150.00 USD
 Expenses:Clothing:Shoes                     +150.00 USD

小红去国外出差了,回国前为了把兑换的美元现金花掉,忍不住又在免税店大肆购物,结果现金不够,于是 300 美元的商品用现金支付了 200 美元,用信用卡支付了 100 美元。小红的信用卡开通了外币消费人民币入账功能,刷美元也出人民币账单。

本例中,涉及到了合并付款和货币转换。小红的信用卡被扣掉了 650 人民币,这其实是由 100 美元转换而来。在 Beancount 中使用 @@ 即可连接两种互相转换的 commodity。在本次交易中,负数共 -200.00 USD + (-100.00 USD) = -300.00 USD,正数共 +150.00 USD + (+150.00 USD) = +300.00 USD,正负相加依然得到的是 0。

使用 bean-query 进行复杂查询

bean-web 的朴素 Web UI 和 fava 的华丽 Web UI 已经能展现很多有用的财务报表,满足大部分用户的需求,如果用户需要进行一些更复杂的数据统计,比如「我 2015 年吃过的饭店按次数排列」,则可以使用 bean-query 工具用 SQL 语句进行查询,详见 Beancount 作者的文档:Beancount – Query Language。这是一个用来统计光顾麦当劳次数的例子:

bean-query-mcdonalds-blurred

六、Beancount 最佳实践

目前我的 Beancount 账本中已经导入了好多个月的数据,在使用过程中也总结了一些最佳实践。以下内容说说我个人是怎么用 Beancount 的,它们中的一部分或全部或许可以为你所用。

编辑器支持

Beancount 的作者是 Emacs 用户,因此自己写了 Emacs 插件。Vim 用户可以使用第三方的插件:nathangrigg/vim-beancount。安装插件之后会为 *.bean*.beancount 文件加上语法高亮和账户名字补全(比如输入 I:S:S 即可补全出 Income:SomeCompany:Salary),还可以将货币那一列的小数点自动对齐。以下是我的 .vimrc 中相关的配置:

let b:beancount_root = '/path/to/your.beancount'
autocmd FileType beancount inoremap . .<C-O>:AlignCommodity<CR>
autocmd FileType beancount inoremap <Tab> <c-x><c-o>

其他编辑器如 Sublime 等也有各自的插件,请自行 Google。

开户日期的选择

账户的开户日期需要在该账户第一笔交易之前。小明为了省事将所有的账户全部开在了 1970-01-01 这个日期。其实可以有一些更有创意的选择:

  • Expenses 账户可以使用自己的生日作为开户日期;
  • Income 账户下可以按来源分类,如 Income:SomeCompany:Salary, Income:AnotherCompany:Salary 等,然后以公司入职时间作为开户日期;
  • Assets 和 Liabilities 账户中的借记卡和信用卡,可以以在银行的开户日期作为 Beancount 中的开户日期,如果记不得具体日子,写成那个月的 1 号也行。

不要惧怕开账户。即使是一些短时间用的小账户(比如只用两个月的储值卡),也可以开账户,因为账户是可以关闭的。关闭后的账户不会出现在关闭后的报表里,不会触发你的强迫症……

多货币账户

在 Beancount 中,一个账户中可以有多种 commodity,比如现在小红的 Expenses:Clothing:Pants 账户就存放了 200.00 CNY 和 100.00 USD。她在出差前想必 Assets:Cash 里也同时存在着 CNY 和 USD 两种 commodity。

如果有多货币的使用,建议将自己主要使用的货币定义到账本中,在账本中添加 option "operating_currency" "CNY" 这一行即可将 CNY 定义为主要货币,在 bean-web 和 fava 中会单独列出来,而其他的 USD、CAD、JPY 等则会列到 Other 里。主要货币可以定义多个。

另外,在账户开户的时候,可以在账户名后面跟上这个账户里允许出现的货币的名字。如人民币-美元的双币信用卡,消费 JPY、CAD 等其他货币的时候,也是以 USD 入账的,为了防止自己在记录一些外币交易时忘记转换货币或是搞错账户,可以在开户时写成 2012-01-01 open Liabilities:CMB:CreditCards CNY,USD 这样,限定这个账户里只能出现 CNY 和 USD 两种货币,如果不慎记入了其他货币,Beancount 会报错。单币信用卡同理,如果你的信用卡不管刷什么外币都是以 CNY 记账,可以在开设账户的时候加上 CNY 这个限制,防止出错(不小心把外币消费没加 @@ 直接记进来)。

账本文件的分割

随着时间的积累,账本文件会越来越大,编辑起来不太方便。Beancount 有 include 语句,可以在一个账本文件里包含另一个账本文件。我的主账本文件里只有一些 option 条目,其他都是 include,各种打开/关闭账户的的条目放单独的文件里,然后每个月的账本是一个单独的文件,也 include 进来。

Beancount 会把所有交易都读到内存里后按日期重新排序,所以每条交易在文件里出现的顺序并不重要。

导入银行账单

不同人的记账习惯不同,有的人喜欢消费完一笔立刻就记账,有的人喜欢定期(每天、每周、每月)把之前的收支汇总到账本上。在我看来,所有「有据可查」的交易,如走银行卡的交易,是可以定期汇总的,但是那些无账单无票据的交易,如现金交易,要么就是干脆不记,要么就应该想办法立刻记下来,否则当你定期回忆的时候一定会因为各种原因出错,从而打击记账的信心。

更新:如果你喜欢发生一笔交易立刻就记下来,而不是定期导入账单,并且你是一位 OS X 用户,可以试试 @blaulan 制作的 Alfred Workflow:blaulan/alfred-beancount

就我自己而言,因为不喜欢现金找零,我会尽量避免现金消费,我的绝大部分交易全部都是刷卡消费,全部都能从银行账单里查到,现金类交易每月通常不超过 10 笔,因此我的记账主要是靠导入银行账单。现金部分则是在手机上安装一个简单的记账软件,里面只有一个账户,就是我的现金账户,每月那少量的现金交易就立刻用它记下来。由于对功能性要求非常少,几乎任何一个手机记账软件都可胜任,随便挑一个就行。

那么怎么导入银行账单呢?一些银行提供了 OFX(Open Financial Exchange)格式的账单,Beancount 可以直接导入,但是据我观察,中国大陆的银行没有一家是支持 OFX 的,都是自己搞一套自己的账单,能有个 CSV 导出已经不错了。所以只能自己写脚本解析了。这是我写的导入招行信用卡账单的脚本:cmb_credit_cards.py

由于 Beancount 的账本是文本文件,将银行账单转换成 Beancount 账本只是对字符串的操作,这方面各种脚本语言都可以大显神通。我自己的做法将银行账单中的每笔交易的日期、内容和金额提取出来,全部拼成从该账户往 Equity:Uncategorized 里倒豆子的交易,然后再用 Vim 配合插件手工将账户名根据实际情况改好。这样的操作我每月末做一次。处理完银行账单之后,我在后面再追加现金交易,将手机中的记录中的那几笔现金交易录入到账本里。由于 Beancount 会对交易按日期重新排列,所以直接追加到后面即可,不用管文件中的顺序。

值得一说的是,现在一些第三方支付服务很流行,比如杭州某公司推出的带聊天功能的支付服务、深圳某公司推出的带支付功能的聊天服务等。这些支付服务也会发所谓的「对账单」给用户,我对它们是一向无视的。我在第三方支付服务里是不留余额的,所有的「经过」第三方支付服务的交易都是从银行扣款的,因此我导入银行账单就够了,不用再导入第三方支付服务的账单。

导入银行账单时,需要注意的一个地方是去重。如果两个银行账户间有转账操作的话,会出现重复的账目,比如用借记卡对信用卡进行还款,在导入的借记卡账单和信用卡账单中都会有体现,然而这两笔交易其实是同一笔,这时候就需要去重。

我现在每月账本由三部分构成:

  • 信用卡账单(从银行账单导入后配合 Vim 插件半自动填写 Expenses 账户)
  • 借记卡账单(从银行账单导入后配合 Vim 插件半自动填写 Income 账户)
  • 现金交易记录(平时用手机记录,月末手工录入)

我的每次导入只需要去重两次,一次是每月借记卡自动还款信用卡,一次是每月 ATM 取款。如果你的账单构成比较复杂,是时候考虑优化一下了,比如第三方支付服务里不留余额,省得还要导入它们的账单并去重……

更新:我开设了一个 GitHub 项目 awesome-beancount,用于收集私有格式账单的导入脚本,如何下载各银行账单,以及其他的一些 Beancount 的最佳实践。目前里面已经有了一些用户分享的中国大陆银行及第三方支付服务的导入脚本。如果你使用的银行已经在这个项目里了,你可以直接使用;如果不在,你可以写出你自己的导入脚本并提交一个 pull request。

定期断言

一本维护良好的账本应当定期做断言(assertion),标记在某个日期某个账户(通常是 Assets 或 Liabilities 账户)里有多少豆子。断言的例子如下:

2016-02-01 balance Assets:Cash 500.00 CNY
2016-02-01 balance Assets:Cash 100.00 USD
2016-02-01 balance Assets:CMB:C1234 1000.00 CNY

断言语句告诉 Beancount,这个账户在这个日期凌晨 00:00:00 时间点(也就是前一天深夜 24:00:00),余额为这个数字。小红账本里以上断言告诉 Beancount,截止一月底,小红钱有 500 人民币、100 美元的现金,同时招行尾号 1234 的借记卡里有 1000 人民币的存款。

Beancount 的时间精度是「日」,所以这里必须强调,诸如 open, close, balance 等带日期的语句,均发生在当日的第一笔交易之前,你可以想像它们都是在凌晨发生的,而普通的交易都是发生在白天。因此,要断言一月份的余额,日期应写作 02-01 而不是 01-31。同样地,信用卡等通常为负数的账户也能进行断言,比如小红的信用卡账单日为 20 日,2 月份账单应还款 5000 元,那她的断言应该这样写(注意日期是第二天,也就是 21 日):

2016-02-21 balance Liabilities:CMB:CreditCards -5000.00 CNY

添加了断言之后,Beancount 便会检查那个账户的数字是否与断言的数字相等,如果不相等就会报错。人总是会犯错的,当你因为各种原因在账目上出现了错误,断言能帮助你缩小查错范围——你只需要检查最后一次成功的断言之后的发生的交易即可。

合理填充

Beancount 另一个有趣的功能是填充(padding),填充是配合断言一起用的,当 Beancount 解析到填充语句时,会自动在这条语句和下一条断言语句之间插入一条填充交易,使得断言成功。在填充语句所在日期和断言语句所在日期之间不能再有其他交易。例子如下:

2015-11-30 pad Assets:Cash Expenses:Food:Drinks
2015-12-01 balance Assets:Cash 200.00 CNY

小红 11 月底做账目核对的时候,发现钱包里的现金是 200 元,但是根据 10 月底的余额,以及 11 月的交易记录,钱包里应该剩 200 多元才对,她想了下,可能是有几次在路边买了饮料喝忘记记录了,因此她使用填充功能来解决这个问题,在 11 月最后一笔交易和 12-01 的断言之间插入一条 pad 语句,这样 Beancount 便会自动插入一条交易,使 Assets:Cash 里的余额调整为 200.00 CNY,而因此产生的货币变化,则记录到 Expenses:Food:Drinks 账户里。在本例中,自动插入的交易内容即是从 Assets 账户倒出了一些货币到 Expenses 账户里。

Beancount 作者便是这样来使用的填充功能的。他的现金账户几乎只用来购买烟酒和饮料,但是他又懒得记录现金支出,于是他就在月底的时候将现金账户 pad 到 Expenses:Food 一次,然后用断言语句记下月底现金账户的实际余额,中间的差值会由 Beancount 自动算出来并插入。

填充功能另一个用途是开户时设定初始余额。比如小红的借记卡是 2010 年开户的,她从 2015-06-01 开始用 Beancount,她就可以这么写:

2010-01-01 open Assets:CMB:C1234
2015-05-31 pad Assets:CMB:C1234 Equity:Opening-Balances
2015-06-01 balance Assets:CMB:C1234 1000.00 CNY

这样 Beancount 会自动插入一条交易,把 Assets:CMB:C1234 在 2015-06-01 (凌晨)的余额的调整为 1000.00 CNY,因此产生的货币变化(新开的账户余额默认是 0),记录到 Equity:Opening-Balances 账户里。在本例中,自动插入的交易内容即是从 Equity 账户倒出了一些货币到 Assets 账户里。

填充功能比手工写一笔交易有什么好呢?你不需要去计算两个数字之间的差额了——Beancount 会自动算出差额并帮你插入交易。此外,这个差额是动态计算的,在上面两个例子中,如果小红想起了在哪天买了什么饮料,重新记上去,那么这个差额会自动变小;如果小红后来又导入了 2010-01-01 到 2015-05-31 之间的账单,那这个开户余额也会自动根据实际情况调整大小。

七、尾声

我从 2011 年大学一年级开始用手机记账,至今也快五年了。其间换过几次记账软件,但其实一直在凑合着用,因为这些记账软件总有一些功能没有办法覆盖到,因此我总是想着各种方法去曲线救国。比如最常见的与室友出去 AA 聚餐,一个人付钱,其他人把钱给付款人——这种交易在付款人的银行卡里是体现为一笔交易,但是实际上在软件里却要为每个单独记一笔,否则到月底一看,「哇,我这个月在吃饭上花了这么多钱」,其实只是把帮别人付的钱都加入了「聚餐」这个类别而已。但如果真的按照实际情况付一次钱记多笔的话,拿银行卡账单对账的时候又会让人很焦躁。

大约在 2015 年,我开始逐渐意识这些困难不是记账软件本身的问题,而是记账方法的问题。我用过的那些手机记账软件,不管 UI 多么好看,它们本质都是个单式簿记系统,因此只能处理「一对一」的简单交易,像 AA 聚餐、合并付款这种「一对多」和「多对一」的交易,就没法合理优雅地记录了,更别说上文小红和小美在黑心饭店遇到的「多对多」交易了,根本应付不来。

于是我开始接触复式簿记。发现手机上唯一堪用的复式簿记软件是 GnuCash 的 Android 版。然而它虽然堪用,却不堪重用——Bug 实在太多了。Beancount 作者曾吐槽过 GnuCash 的电脑版,他说他每隔几个月就会去尝试一下它,但总能在一小时内发现新的 bug。电脑版尚且如此,手机版的 bug 数量更难想像——这些有着复杂 GUI 的传统复式簿记软件太难用了。

在 2015 年底的时候,我看到 @yegle 提到了 Beancount 这个软件,便去了解了一下,这才打开了新世界的大门——这才是「double-entry accounting done right」啊!没有复杂的 GUI,只有亲切的 CLI、强大的功能、简明的语法。这才意识到原来复式簿记可以如此简单好用。

用了几个月 Beancount 之后,我对它十分满意,因此写了这么一篇博客,希望能将它推广给更多的用户。

高考结束

公元 2011 年 6 月 9 日 16:40 UTC+8,江苏省的大部分高级中学里传出整齐划一的一阵铃声,伴随着这阵铃声的是两万多江苏考生如释重负的心情,因为高考结束了。

作为一个应届江苏考生,以上的心情我也同样经历了。于是我感到十二年的寒窗苦读算是告一段落了。

出于对自己的“严格要求”,我从 4 月 21 日开始就不再使用手机,且不再上网、发,这导致我与信息世界基本失去了联系。高考结束了,我再次接入了网络,看到了:

  • Gmail 收件箱里三位数的未读主题(这只是收件箱,还不包括其它的标签里的未读邮件:光是 Gfans 就有三位数的未读主题了);
  • Twitter 上好几页的 Replies,基本都是推友的高考祝福;
  • Google Reader 里 1000+ 的闪亮提示(幸亏它是这么显示的,否则估计要五位数了);
  • Facebook 上好几条未读私信和好友请求;
  • Gtalk、Foursquare 等上也堆积了好友请求并且被夺去无数 Mayorships;
  • 连 TBBT 第四季都出完了!
  • ……

更重要的是,本博客也好久没有更新了。上一篇文章还是 2011 年 1 月份发表的,这让我愧疚。高三之后的确忽视了博客的更新。现在来到了后高考时代,整个“暑假”都成了我的牡蛎(这句话是怎么溜出来的?),我想我有足够的时间来完成这些事务了。

但事实也并不完全如此,考后的我有点失去重心,做事不如以前那么有条理了(我以前有条理吗?),最直接的表现就是这篇《高考结束》拖了两天才写……

嗯,为了激励自己一定要把博客更新下去,我先把未来准备写的几篇文章列在下面吧……目前它们的链接都是不存在的,不过以后它们会一个接一个变为可点击的超链接的……

最后解释一下有点无厘头的题图,那是我在写这篇文章之前搜图片时看到的情景……那个黄色背景的提示深深地刺痛了一个 2Mbps ADSL 用户的心……这是来自下行速度 523.96Mbps 的 Google 对下行速度 2Mbps 的 wzyboy 的鄙视啊……

教程:如何制作一个多功能U盘

想制作一个强大的多功能U盘吗?一个4GB的U盘,可以启动PE进行系统维护,可以快速安装多个版本的GhostXP,还可以原生冷启动安装Windows 7,甚至还可以以Wubi方式装Ubuntu……

这一切并不难,如果你有过一些折腾系统的经验的话,跟着下面的教程走,很快你也能拥有这样一个U盘。

本文关键字:量产,多功能U盘,可启动U盘,PE,GhostXP,Windows 7,Ubuntu

一、准备

你需要:

  1. U盘一只。一般要求4GB或以上,不要太“非主流”就行了,像那种“米老鼠牌U盘”之类的山寨产品不在本文的讨论范围之内。一般Kingston的U盘就行,因为接下来要用到量产,所以选择一款主流主控芯片的U盘还是比较重要的。
  2. 电脑一台。要求有USB 2.0全速接口,操作系统最好是Windows XP,因为根据我的测试,部分芯片工具和量产工具在Windows 7是无法正常使用的。如果你没有XP,装虚拟机也是可以的,推荐VirtualBox,但是不保证成功率。(我成功过一次,很麻烦)
  3. 系统镜像(.iso文件)若干。至少要有:
    • 一个PE系统镜像,推荐“老毛桃PE之撒手不管版”。这个版本大概100+MB的样子,我自己在这个镜像的基础上做了一些小修改,使之缩小到了79MB。
    • 若干个Windows XP的Ghost版镜像,这类镜像网上多的是,这里说一下,如果你喜欢纯净版的GhostXP,那推荐雨林木风的,如果喜欢带有软件的GhostXP,那推荐深度的,至于其它什么番茄花园、JuJu猫、电脑公司之类的,强烈不推荐
    • 一个Windows 7 Ultimate RTM镜像,这个最好是原版的,目前我尚不推荐有修改过的Windows 7,这里提供一下原版镜像的BT种子:12
    • Ubuntu 10.04镜像文件。这个从Ubuntu的官方上下载。(点此下载
  4. Windows 7 USB DVD Download Tool。这个微软官方也有下载,此软件很小。下载地址:点此下载
  5. 数码之家出品U盘主控芯片检测工具。下载链接:点此下载
  6. 对应的量产工具。这个在下面会有介绍。
  7. 耐心、好奇心和求知欲。

二、思路

思路如图:

1

三、步骤

下载量产工具

用上面的提到的主控芯片检测工具检测出U盘的主控芯片并下载量产工具。这个主控芯片不同的U盘差异很大,有时同一型号的U盘,只因为生产的批次不同,芯片也是不同的。我的Kingston DataTraveler 4GB的主控芯片是SK6211的,我下载到的量产工具名字就叫SK6211_20090227_BA。

量产U盘

量产原本是工厂里的一道操作工序,但是现在由于一些软件泄露等等,普通用户也可以量产了。我们量产的目的是把U盘分割为一个光盘加一个U盘。什么意思呢?我们的普通U盘插到电脑上就显示为一个U盘的图标,但是量产出多重扇区的U盘就不一样的,插到电脑上之后显示为两个图标(两个驱动器),一个是光盘,一个是普通的U盘。那个光盘驱动器就和真正的光盘一样,只能读文件,不能写文件、删文件,并且一般的格式化是奈何不了它的,就连大部分的专业分区工具(比如PM)都识别不了它的文件系统(在Windows里显示为CDFS),最棒的是,这是底层的,这意味着它在BIOS里也是可以识别的,如果光盘是可引导的,那么就可以用它来启动系统。

具体怎么量产呢?由于不同的U盘的量产工具不同,操作也有一定的差异,我这里不能一一列出,大家可以以“自己的主控芯片或是U盘型号+量产教程”为关键字搜索,这里我就讲一下大概的过程。注意:量产U盘会使U盘内所有数据全部丢失,所以量产前一定要备份数据。

  1. 插上U盘,等待U盘被系统识别完毕。
  2. 运行量产工具,第一次运行时可能会自动安装一个驱动,需要耐心等待它安装完成。如果量产工具是正确的话,会提示找到设备,这时候点击“设定档”之类的按钮,输入密码(默认是12345或123456),然后点击“多重扇区”,多重扇区里会有一个选择iso的地方,在这里选择一开始的那个PE镜像,并且确保“使CD可启用”之类的选项是打勾的,然后确定,回到上一界面。
  3. 在这一界面里还有一些个性化的设置,比如U盘上的LED灯闪的速度啊,U盘的名字啊之类的,改成自己喜欢的,然后保存确定,再回到上一界面,也就是主界面。
  4. 这时一切已经就绪了,点击“开始量产”之类的按钮,量产过程就开始了,视PE镜像的大小而定,这个过程要1-2分钟,结束之后拔下U盘,关闭量产工具,重新插上U盘,如果成功的话,那么应该是这个样子的:
  5. production-result-in-explorer

    production-result-in-diskmgmt

    boot-priority-in-bios

    第一张图是在Windows资源管理器里的样子,可以看到已经被识别为两个设备了

    第二张图是在Windows磁盘管理里的样子,也可以看到是两个不同的设备,而不是普通的U盘分区。

    第三张图是在BIOS里的样子,也被识别为两个设备,一个是USB-CDROM。一个是USB-HDD。(注:U盘的名字在量产的时候被我改了……)

  6. 成功。

使量产后的U盘成为可启动的USB-HDD

微软提供了一个官方的制作Windows 7安装U盘/DVD的软件,就是上面提到的Windows 7 USB DVD Download Tool,用它可以将下载到的Windows 7的iso文件安装烧录到DVD里或者是写入到U盘里,这里我们用的是后一种功能。

  1. 安装Windows 7 USB DVD Download Tool(以下简称DT)。
  2. 插上U盘,等待U盘被系统识别完毕。
  3. 运行DT,第一步是选择镜像:
  4. windows-7-usb-dvd-download-tool-step-1

  5. 第二步选择USB Device。
  6. windows-7-usb-dvd-download-tool-step-2

  7. 第三步选择你的U盘,注意别选错了。
  8. windows-7-usb-dvd-download-tool-step-3

  9. 然后点击Begin copying,DT就会将你的U盘格式化成NTFS文件系统,然后写入镜像文件,这一过程在10分钟以内。
  10. 当DT提示写入完成时,可冷启动安装Win7的部分已经完成。这时你打开U盘,会发现里面已经多出好多文件,这些都Win7的安装文件,不要删除它们。如果你觉得它们碍事,请点击这里查看解决方法。

复制其它文件到U盘里

最终的U盘还能用来装GhostXP和Ubuntu的,接下来怎么做呢?这两个就方便多了。

  1. 把下载到的雨林木风的或者是深度的GhostXP的iso镜像用WinRAR或者7-Zip之类的压缩软件打开,一般在根目录里,或者是在SYSTEM目录里有一个非常大(大约600-700MB)的扩展名为 .gho 的文件,选中它,然后解压出来,为了以后不混淆,可以把它改名成一个简明的名字,比如ylmf.gho或者是deepin.gho之类的(注意是英文命名,用中文命名的后果自负),然后你可以在U盘根目录新建一个名为 _gho 的目录。之所以用下划线开头,是为了这个目录可以在大部分情况下排在第一个……再把那些gho文件复制进去。
  2. Ubuntu的镜像就更加容易了,可以直接把iso文件复制到U盘里,搞定。

三、使用说明书

多功能U盘已经完全制作好了,如何使用呢?

PE系统维护功能

关机,插上U盘,启动电脑,现代的大多数主板都支持在开机的时候按一个键(一般是F12)选择启动设备,如果不支持的,那就进BIOS改。按下了F12之后,会出现一个Boot Menu,如图:

boot-menu

在这里列出了一部分可启动的设备,选择USB CD/DVD,即可进入PE系统。值得一提的是,U盘的读速度比光盘要快,所以进入PE系统是非常快的,而且没有光驱读盘时那巨大的噪音。

冷启动安装Windows 7

关机,插上U盘,启动电脑,按F12或类似按键打开Boot Menu,如上图,选择USB HDD,即可启动Windows 7冷启动安装过程,这种安装方式是可以进行分区等操作的,也可以用来把Windows 7灌装到一个vhd(虚拟硬盘)文件里。而如果是在PE或其它Windows OS里用虚拟光驱加载Windows 7的安装过程,则是不能够分区和灌装vhd的。

快速安装多个版本GhostXP

这是给别人去装机时经常用到的,很多人的系统已经完全瘫痪无法启动了,用这个U盘就可以解决问题。关机,插上U盘,启动电脑,打开Boot Menu,选择USB CD/DVD,进入PE系统。进入PE系统后可以进行一些简单的备份、整理操作,比如重新分区之类的,顺带把逻辑分区根目录里的autorun等不该出现的东西删除之,然后运行PE系统里的Ghost32,选择你放在U盘里的gho文件,恢复之即可。我曾经碰到过我的语文老师要重装系统,当我带上光盘去的时候,我才发现她的笔记本电脑比较古老,是没有光驱的那种,从硬盘装XP就有点麻烦了,要是装Win7的老师又用不惯……用这种多功能U盘就可以解决这一问题。另一个优点是可以自主选择用什么版本的镜像文件,根据不同用户的需求装不同的版本

用Wubi方式安装Ubuntu

如果有人想尝鲜下Ubuntu,用Wubi方式安装是最好的了,这种方式安装,所有的文件都是安装到一个或两个扩展名为disk的文件里,不用给硬盘重新分区,不用怕数据丢失。安装和卸载都是在Windows下完成的,和安装卸载一个普通的Windows程序一样方便。

用这种方法装Ubuntu,这个U盘也可以做到。同样启动到PE下,然后运行虚拟光驱软件,加载Ubuntu 10.04的镜像,再运行光盘根目录里的wubi.exe,选择Install inside Windows即可。

小结

至此,这个多功能U盘已经算是完成了,可以PE,可以冷启动装Win7,可以装XP,还可以尝鲜Ubuntu,当然,用它来存储数据也是可以的。如果对此继续感兴趣的话,可以阅读下面的内容。

四、进阶:给USB-HDD分区

之前说过,DT会使你的U盘根目录充满了Windows 7的安装文件,这多少有一些碍事——如果你还想用的U盘存储普通资料的话。此外,用DT制作出来的U盘必须是NTFS的,但是这个文件系统并不适合U盘,特别是需要写入数据的时候,NTFS的日志式操作会使U盘的寿命大大减少。最适合U盘的应该是exFAT文件系统,但是就目前来看,这一文件系统尚未普及,所以我们一般选择FAT32文件系统作为U盘存储数据的分区的文件系统。

那么,如何让U盘既能装Win7,又适合于装数据,并且不再一大堆安装文件碍眼呢?那便是分区。

需要说明的是,之前的量产并不是真正意义上的分区,而是调用了U盘主控芯片的一些功能,使一个U盘设备模拟成了两个设备,后面那个是一个纯粹的U盘,是可以和正常的存储介质一样分区的。

分区工具很多,这里以PM为例介绍。PM不用费力去找,在PE里一般都是有的,懒人主义者可以按照上面的方法启动到PE里再运行PM,然后对U盘进行操作,也可以用7-Zip之类的工具解压出PE镜像里OP.WIM里的PM绿色版程序,具体方法这里就不再叙述,下面讲一下如何分区。

这里的分区方法是假设你还没有用DT做过U盘的,如果你已经用DT做过U盘了,做完下面的分区操作之后你又要重新做一遍了,不过量产出来的光驱却是不用担心,因为一般的磁盘工具是奈何不了它的。

  1. 打开PM,应该会找到两个物理磁盘,一个是电脑的硬盘,一个是U盘,至于那个量产出来的光盘,这里是不显示的。
  2. U盘应该只有一个分区,右击,删除分区。
  3. 在自由空间里右击,创建一个主分区。Windows 7的安装镜像有2.32GB,所以这里创建一个2.4GB的分区就够了,文件系统任意,反正待会儿都要被DT格式化成NTFS。
  4. 在剩余的自由空间里右击,再创建一个分区。按说这里应该创建扩展分区,但经过我的测试,创建扩展分区并不能真正成功,所以这里创建一个主分区,文件系统为FAT32。
  5. 点击左上角的按钮,应用所有操作,等待它完成即可。

在完成这些操作之后,重新插拔U盘,这时应该可以看到在资源管理器里有三个图标了:一个是光驱,另外两个是硬盘分区。这时再运行DT,把安装文件复制到2.4G的那个分区里,完成。从此以后,这个2.4G的分区就用来装Windows 7,而剩下的那些空间的FAT32分区,则是用来存储数据的。

五、注意事项及祝福

  1. 再次提醒要备份好U盘里的数据,因为量产和重分区都是要擦除所有数据的。
  2. 在Ghost32里,或是PM里,选择磁盘的时候造成不要把电脑里的硬盘当成U盘选择了。如果实在分不清就看容量大小好了,现代的硬盘的容量比U盘大多了。
  3. 量产的成功率并非百分之百,一个主流的主控芯片是很重要的,选择对量产工具更加重要。如果万一量产失败了,可以搜索一下恢复教程,再量产一遍,就可以把U盘恢复成量产之前的样子。
  4. 祝各位读者制作成功。

六、尾声

好久没写系统方面的文章了,今天写了一篇,希望对大家有帮助。

转载时请注明本文的原始地址:http://wzyboy.im/post/281.html

Update: 有些读者反映用DT在量产后的U盘写入镜像会出错. 我找了一些别的U盘试试, 果然有此现象, 不过用UltraISO写入, 没有问题的. 如果DT写入有问题, 可以试试用UltraISO写入, 方法是用UltraISO打开Win7的镜像, 然后在菜单里选择”写入硬盘镜像”, 先 在”快捷启动”里写入一个Vista的引导, 然后再写入镜像, 写入完成后就好了.

Android替换字体时需要注意的一些问题

本文关键字:Android, 字体消失, 替换字体, 雅黑, 微米黑, 丽黑, 兰亭黑

一、Android字体简述

Android是一个典型的Linux内核的操作系统。在Android系统中,主要有DroidSans和DroidSerif两大字体阵营,从名字就可以看出来,前者是无衬线字体,后者是衬线字体。具体来说,一共是这几个字体文件:(位于 /system/fonts 目录下,需要root权限查看)

  • DroidSans-Bold.ttf   粗体的无衬线字体(拉丁字母等)
  • DroidSans.ttf   常规的无衬线字体(拉丁字母等)
  • DroidSansFallback.ttf   常规的无衬线字体(中文字符等)
  • DroidSansMono.ttf    等宽的无衬线字体(拉丁字母等)
  • DroidSerif-Bold.ttf    粗体的衬线字体(拉丁字母等)
  • DroidSerif-BoldItalic.ttf    粗体+斜体的衬线字体(拉丁字母等)
  • DroidSerif-Italic.ttf    斜体的衬线字体(拉丁字母等)
  • DroidSerif-Regular.ttf    常规的衬线字体(拉丁字母等)

除了这些字体文件,有些修改版本的 ROM,比如 CyanogenMod 5.0.8 ,还会有 Clockopia.ttf 之类的文件,从名字就可以看出这是显示数字时钟时用的字体。

上面提到的这些字体中,除了 DroidSansFallback.tff ,其它的一律不包含中文字体。而 DroidSansFallback 中的中文字体,是一种类似于文泉驿微米黑的字体,只是它的字库非常小,字符数量很少,估计只有 GB2312 的字符集,稍少见一些的字都是不包含的,比如我同学名字里有“祎”这个字,在联系人里就显示为一个方框。

二、替换字体

这样的小字库是不能令人满意的,而且,这种字体看久也觉得厌了,特别是在 iReader (Android电子书阅读软件)中看久了,就会觉得笔划越来越粗,长时间看了就疲劳了。那么替换字体也就显得很自然了。Android用的是标准的TrueType字体,因此只要把电脑里满意的字体改名后直接复制到 /system/fonts 目录下,用root权限覆盖掉原来的字体文件就好了。

我在 这篇文章 中提到过,我尝试了替换字体,我一开始是用的真正的Unicode的微米黑字体(这个字体的大小是4MB,系统原来的是3MB)替换的。但是有一些奇怪的问题。具体表述如下:

  1. 只替换DroidSansFallback.ttf,则一些尝试调用DroidSans和DroidSans-Bold的地方的字体都会显示为方框,整个手机只有英文字体和一小部分的中文字体可以显示,其它大部分都是方框。
  2. 如果把 DroidSansFallback, DroidSans, DroidSans-Bold 三个字体文件都替换,则不会有方框,但是系统里再也区分不出粗体与普通字体了。而且还要考虑到字体文件大小的问题,有些版本的ROM刷了之后 /system 下的剩余空间是很小的,放三个大字体根本不现实。(后面要提到的丽黑是8MB,兰亭黑是7M,雅黑则是14MB到20MB不等)
  3. 最诡异的来了,替换字体后,刚开机是没有问题的,问题就在过了一段时间后,所有的字体都会消失!所谓消失,就是所有该有字的地方都变成了空白,空白一片。这种情况出现的没有任何先兆,一般是开机用了一段时间就会有这种情况出现。如图:

android-font-missing-1 android-font-missing-2

android-font-missing-3 android-font-missing-5

android-font-missing-4

这种事情真是非常的令人讨厌,我还因为这件事情误格式化过一次microSD卡。(因为看不见按钮上的字)

三、问题何在?

出现了这种问题,自然要想办法去解决。我仔细观察回想了一下,一般都是开机之后运行了一些比较耗RAM的程序才会出现这种问题。比如开机之后运行Opera Mini 5,打开12个标签以上,右上角的时间字体就会消失,Action Bar里的文字也会同时消失,这种问题就重现了。考虑到RAM用了多了之后字体就消失了,我推断事情是这样的:开机载入了字体,放在RAM里,但是这种新替换的字体比系统自带的字体文件体积大很多,所以需要更多的RAM,而在运行了一些大程序之后,RAM不够了,字体文件就被踢出了RAM。

真的是这样吗?我当时认为的确是这样的,所以我干脆决定不用新字体了,就用老字体吧,就把之前备份的老字体文件全部用Root Explorer覆盖回去了,重启。现在应该没问题了吧?我故意运行了几个大程序……结果问题又重现了。

这就奇怪了,最早用老字体是从来没有这个问题的,换了新字体之后问题出现,但是用备份文件恢复到老字体,怎么还是有问题呢?

在后来相当长的一段时间内,我一直想不通这件事情,甚至还愤怒地把DroidSansFallback.ttf给删除了……当然,开机不能了。

重新用update.zip恢复了一下,冷静下来思考,替换前和替换后有什么是改变了的呢?在Root Explorer再仔细看看……似乎是权限改变了!

Linux文件系统里的文件都是有权限的,且与Windows文件系统里的权限概念有一些区别。三个权限者,Owner, Group, World(Other),每人都有三项权限,分别是 Read(r), Write(w) 和 Execute(x) 。其中代表Read的数字为4,代表Write的数字为2,代表Execute的数字为1,这样就可以用1-7的自然数还表示不同的权限。比如R+W就是6,R+W+X就是7,R+X就是5,等等。三个权限者分别排列,就有了755,777等这样的代表权限组合的数字。

Linux的这套权限系统可以用chmod命令来修改,但是在Root Explorer里修改起来更加方便一些。

长按文件,然后就有一个Permissions的选项,点击之,打开的窗口里就可以改权限了,当然前提是 mount as r/w。

如下面左图,这是 /system/fonts 目录下原生的一个文件,而右图则是从 /sdcard 里复制进去的文件,对照两张图就可以看出权限发生了改变。(由644变成了075)

从图中可以看到,原来的文件对于User来说是有R/W权限的,而从 /sdcard 里复制进来的文件对于User是没有R/W权限的。这就是问题所在了!这也很容易解释,为什么最早不会有字体消失问题,然后替换了字体(从外面复制进来权限不正确的字体)后字体就消失了,但是用备份文件恢复(重新又从外面复制进来字体)后,字体消失问题还是存在!这是因为在复制过程中,由于文件系统的不统一(ext2和FAT32),导致权限错乱。

四、如何解决?

知道了问题的所在,解决起来就非常容易了——在替换完字体之后不要急着重启,一定要对照其它的文件,把新复制进来的那些文件的权限改成正确的644!

五、特别关照

  1. 字体是放在 /system/fonts 目录下的,这个目录属于 /system 分区,因此你先要确保 /system 分区的剩余空间是够的。
  2. /system 分区剩余空间不够怎么办?那就删除自带的程序。由于我做了Apps2SD,所以我的 /data 分区( /sd-ext 分区)的剩余空间是足够的,那么就可以把系统自带的程序移到 /sd-ext 下面?怎么移动呢?参照这篇文章:《如何把Android自带程序移动到其它分区以腾出 /system 分区的空间》
  3. 对于屏幕显示来说,还是无衬线字体比较适合,衬线字体么,还是让它们在印刷上发挥作用吧。我看到有一些人把那些“华康少女简体”之类的手写衬线字体当作Android的默认字体……这实在是不恰当的行为。虽然一开始会觉得很惊艳,但是时间长了之后眼睛会疲劳,对视力并不好。所以还是要选无衬线字体的好。

六、字体推荐

看上面这四幅图,前三张是我在Android上实际截图得到的,分别是用兰亭黑、丽黑、雅黑作为默认字体。最后一张是Windows 7里微软雅黑字体的显示效果。需要注意的是,虽然我的系统里以注册表方式加载了GDI++,但是我在截那张图的时候是用GDI++Inject把Notepad.exe的GDI++模块卸载掉的,所以最后一张图是没有任何额外渲染的本来效果。

都说实践出真知,通过这次替换我才真正尝试出了最适合Android的字体。同一个字体,在不同的系统里、以不同的方式渲染,显示出来的效果也是有很大不同的。比如兰亭黑在Ubuntu下面好看的,在Android下就很一般,丽黑是Mac OS X的默认字体,有多漂亮大家肯定都见到过,但是在Android上的显示效果呢?笔划粗大、粘连,看得时间长了很容易眼疲劳。而微软雅黑这款MS花了重金打造的字体呢?它的Windows下的显示效果……说真的,很差。至少不用第三方字体渲染的情况下很糟糕,可是,当我抱着试试看的态度把它复制到Android之后,我才发现它的显示效果如此之好:字面大、笔划清晰、结构平稳、构架匀称……简直是Android下的极品字体啊。

这样的字体哪里有?心动的同学就不要到网上去搜索了,网上很多是修改版本的,虽然字体小,但是具体改了什么东西我就不清楚了,我不能保证它能像图中显示的那么好看,所以……大家可以像我一样,从Windows 7的字体目录里复制。(默认是 C:\Windows\Fonts )

(全文完)

转载时请标注本文地址,谢谢。

重新刷回Android 1.6了

本来我的HTC Dream被我刷了2.1的固件,但是今天我刷回1.6固件了。

原来的2.1固件太卡,而且不能放视频,任何视频都放不出来。我在电脑上转了很多遍,转出各种分辨率、编码、码率、帧率的,就是不能放。

刷回1.6后,我试着打开最大分辨率的那个视频,完美播放,清晰、流畅。

再打开摄像,不再花屏。

不过也有不爽的一点,字库。
Android自带的字库是不全的,很多生僻字都是没有的,比如“祎”字,只能显示成一个方块。 我在2.1下用一个更加完整的字库覆盖掉原来的字库,不用重启,所有的生僻字都能显示了。刷回1.6后,我先试了下“祎”(我一个同学名字里就有这个字),是方块。然后我用老办法把字库覆盖,再去看那个同学的名字,果然显示出来了。

但新的问题出现了,更多的字变成方块了!!!比如有人给我发短信或是Gtalk信息,那么状态栏是有显示的,那些字里的中文字都是方块!!!还有一些菜单里的中文字,也都是方块。

由于在2.1里没有出现这个问题,说明不是字体文件的问题,而是固件的问题,本来应该调用另一种字号的,调用错了,就变成各种方块了。

目前尚未找到解决方法。Update:爽,Android里部分小字的问题被我解决了。方法:把DroidSans-Blod.ttf, DroidSans.ttf, DroidSansFallback三个文件一起换掉。