价值投资 长期主义 编程 美食 旅行 梦想 参禅 悟道

0%

三国演义中诸葛丞相说:非比夸辩之徒,虚誉欺人;坐议立谈,无人可及;临机应变,百无一能。

无论你自认为路线多么正确,能力多么强健,品德多么高尚…。

实践才是检验真理的唯一标准。

唯有胜仗才能凝聚人心。 在败仗面前一切都会是苍白无力。

无论国家,还是团队,盖莫如是!

什么是最优秀的团队?

顺境的时候,不会沉迷腐朽,不思进取,日渐堕落,而是自我革命,推陈出新,再创辉煌。

逆境的时候,能够打磨重组 砥砺前行,励精图治,百折不挠。每人有着即使倒下一百次,一千次,也要重新挑战第一百零一次,第一千零一次的决心,战略上藐视敌人,战术上重视敌人,合理规划,依势形而定,既可以积小胜成大胜,也可以谋略得当,聚而全歼,一鼓而定全局!

刘邦韩信的部队,教员的部队,是其中最优秀的典范。

复盘,复盘,沟通,批评和自我批评,演练,推进,日拱一卒。

No.1 《穷查理宝典》+《从达尔文到芒格》

No.2 学习三部曲《终身成长》《刻意练习》《认知天性》

No.3 理财书籍《小狗钱钱》+《富爸爸,穷爸爸》+《富爸爸财务自由之路》

No.4 心理学书籍我目前看完的:《被讨厌的勇气》《可爱的诅咒》这两本 1.《对伪心理学说不》 2.《心理学与生活》 3.《进化心理学》 慢就是快!任何捷径,都是歧途。

N0.4 提升逻辑思维的书籍《金字塔原理》+《学会提问》+《超越感觉》+《批判性思维》

真正有效的阅读逻辑是下面这样的:

1.明确自己需要解决什么问题。

2.去互联网上了解哪些书籍可以解决我的问题,或者提供信息,选择好对应的书籍然后带着问题去读。

3.读完后,必须总结你读完后获得了什么

4.定个短期的目标,三个月五个月后,看看行动给你带来了什么反馈,反馈真的很重要

5.根据反馈,进一步反思:怎么进一步优化?也就是复盘复盘复盘!

记住:学习的核心在于行动,同样阅读的核心也是为了指导我们的行动。任何不以行动为导向的都是耍流氓!

《存在主义心理治疗》就是我们反复推荐的书。这本书是当代精神病学大师欧文·亚隆的力作,围绕每个人都会思考的重要人生问题而展开:*死亡、自由与责任、孤独、无意义感亚隆逐一分析了这些问题的本质,论述我们该如何面对。*在豆瓣上的评分高达9.5分,零差评哦。**

《亲密关系》罗兰·米勒

真的是非常推荐这本书,无论你是未曾拥有过亲密关系,还是身处关系之中,又或者已经结束关系。都一定会对你有很多启发。

真的是非常推荐这本书,无论你是未曾拥有过亲密关系,还是身处关系之中,又或者已经结束关系。都一定会对你有很多启发。

1、来自AI专家的13篇必读论文

从《资治通鉴》学到了什么?

通读资治通鉴之后,我最大的体会是人不能对自己掌握之外的人或事抱有任何幻想。任何自我说服或者自我安慰的”应该会,应该不会”都会给自己带来灭顶之灾。列代英明帝王无不如此,妇人之仁只会带来更大的损失和伤害。

作者:中石

特别体现在做决定上,一定要善谋善断,任何决定都是基于所掌握的信息和对形势的正确判断。通观中国历史,每个朝代建立者都值得研究,在他们身上都有很多相同的君主特质。每个君王都是靠战争起家,而在战争中,对形势的判断极为重要,用孙子的话说就是知彼知己百战不殆,也就是要研究敌方情况,我方情况,还有地形友邻等外部情况,将整个态势了然于胸,并根据态势做出决断。但是对于老大来说,最主要的就是要建立起正确的战略方向,战略正确了,个别战役战斗失败才不会影响到整个局势的发展,当然,这里还要涉及到基本盘的问题,基本盘必须稳固

先说战略的选择。先简单说说秦统一v六国,秦朝的统一,是几代人坚持不懈的努力取得的,对于秦朝来说,有一个优势就是打垮西边少数民族之后,主要找略方向就在东方,而当时制定了远交近攻的战略方针,很好的分化了山东六国,使其每次合纵都以失败告终,而在东方,前期主要压力在于三晋和楚,当时要取得对三晋的战略优势,就得先解决楚国,把楚国的势力压缩到东边,使其不能轻易的和三晋联合在一起,要对楚国取得优势,就得拿下巴蜀,对楚国形成了西、南包围之势。解决楚国之后腾出手来解决韩国,长平一战,几乎倾全国之力拿下上党地区,从此对东方诸国特别是赵国取得了战略优势,后面的不过是时间问题。后期,楚汉争霸,刘邦的战略就是还定三秦,东向以争天下。不过项羽打仗实在太牛逼,在那个时代无一是其对手,刘邦是屡战屡败,最后僵持在荥阳一代。好在项羽政治不行,也就是战略还是差刘邦一截,树敌太多,被刘邦逐一拉拢,此时刘邦的战略主要是主力粘住项羽,韩信北上突围,英布、彭越游击于项羽后方,项羽手下无人,不得不亲自出马解决后方,可惜曹无咎不听节度,被刘邦突破了正面。光武帝时期亦然,先打下河北和河东,先向南最后向西。战略的选择不一而足,也不可完全照搬,每次都有其特点。

再说说基本盘,基本盘主要就是经济人才。孙子曰日费千金。没钱很难打仗,钱来源于两个方面,自己的和敌人的,孙子还曰了,要取用于敌。当然,经济上主要还是来源于自己,要有稳固的大后方。上面说到的秦一方面是商鞅变法提高了秦国的生产力,另一方面打下巴蜀之后贡献了很大的税赋和人力,刘邦一直有稳固的关中,项羽后方一直不稳,光武帝有稳固的河北和河东,特别是河东粮草不断。那么在人上,更不用说了,秦国后期人才大多都是他国过来的,刘邦有三杰,光武帝有南阳一众豪族,朱元璋有一众濠泗兄弟,毛教员更是聚天下英才而用之。毛教员说的好,政策定好之后,干部就是决定因素。

在人才问题上,作为领导者,对下属是什么样的尿性一定要心中有数,有的可用于心腹,有的可用于爪牙,有的只能是合作关系。心腹的选择更要慎之又慎,要能够控制得了,很多时候,心腹的反噬比敌人还要致命。当然,要能够控制下面人,没点手段是不行的,任何手段,不外乎,但是在运用上却很微妙,用不好,适得其反。除此之外,能够让人团结在老大周围,捐妻子,冒锋刃,不过是为了有肉吃,没肉吃,空谈理想是不行的,也就是说要实事求是。更重要的还是要具备点英雄气概,何谓?刘四爷,刘裕,高欢,重八,教员等,都是出身寒贱,但天生有那一股子英雄气概,让人倾心相随。

以上可以看出,战略选择和基本盘的重要性。特别在战略上,可以好好看看《论持久战》。

在形式分析上,很多君王并不一定擅长,所以形势分析不一定是自己做的,但是决心必须是自己下的。在判断和下决心方面,不能由别人代劳,别人的选择不一定对自己最有利,形成依赖之后还有被取代的危险,也不能犹豫不选择,宁愿结果错了都不能给人优柔寡断的印象,那样便会拖延时机,最坏的影响是失去威信。当然,在决断上,个人性格起到很大的作用。刘邦在彭城被项羽打爆后,坐在车上一路往西逃,车上除了滕公,还有两个小孩,惠帝和鲁元公主,两个人可是他的亲生骨肉,逃命要车快,刘邦就毫不犹豫三番将两个亲骨肉踹下车,还好每次都被夏侯婴捡了回来,这样的决断力,才是最恐怖的。

能够正确做决断的基础,当然是掌握信息,所以建立直控的信息系统就很重要。孙子言”先知者,不可取于鬼神,不可象于事,不可验于度,必取于人,知敌之情者也。”这里不展开讲。

对于我们普通人来说,有什么用呢?其实用处还是很大的。

这给我们的启示至少有三点:

  1. 人生要有战略方向。人之所以庸庸无为一生,就是没有想清楚自己此生要成为什么样的人,兜兜转转几十年,不过社畜一枚,回眸一望,全是空虚。因此,应该尽早建立自己的人生努力方向,并且持续不断的往这个方面努力。当然,战略方向不是具体的战术,在实际过程中,还会遇到各种问题,就需要机动灵活的去处理。特别是不能出现颠覆性的失误,比如赌博、犯罪、吸毒等。
  2. 要有自己的基本盘。在社会上混,总得有那么一两点拿得出手的本事。你连一个行业的门道都摸不清,就去做生意,就是给别人送钱。在体制内,连抱大腿找靠山时不时不违背道德的前提下拍拍马匹都不会或者不愿做,那也就是一辈子在底层摸爬,过了年龄线只能落寞。在职场上,不管处于什么岗位,你总得弄清这个岗位需要的知识和技能,特别是容易踩的坑。包括在人脉上,要在自己承受范围内,适当的建立一个圈子,生活或者工作中用得到的人保持适当的联系和人情往来,圈子的回报是长远的和无形的,千万不要舍不得小钱而遇事的时候无人可求。基本盘,不一而足,需举一反三。
  3. 任何事情都要掌控在自己手中。这就需要对事务的基本分析和判断能力,这个能力不难,把《矛盾论》和《实践论》看上十遍,把《毛选》看上三遍,自然就有体会。对自己,对和自己有关的其他事情,必须主动收集相关信息,并加以分析,从中找到对自己最有利的处理办法。千万不能无动于衷或者托付给他人(包括你最亲近的人)。可能最难得是信息的收集和掌握,所以上面说的圈子的重要性。比如体制内提职问题,首先要考虑单位里面人员层次结构,什么时间点可能空出的位置,空出的位置需要什么条件,自己能否达到,或者自己的条件到什么时间点才可能抅到什么位置,谁具有决定权,我应该在他身上如何用力,我的竞争对手是谁,对他,我要防止什么,什么话不能说等等。做生意,行业内基本门道要弄清楚,行业内重要人员来往,时不时吹吹牛逼,收集信息。等等。总的一点,收集信息——分析整理——定下决心——集中火力,没有攻不下的山头。

特别是在职场之中,面对复杂的职场斗争,你是把自己的前途交给上级和同事呢,还是面对任何问题都自己先分析一番,想办法搜集信息分析判断,直到自己心中有数。比如说,上级交代你和另一个人去完成一件事情,你必须亲自参与到整个过程,对每个环节都必须过目掌控,如果偷懒或者过于相信你的搭档,十有八九就会被坑。比如有一个新职位空出来了,自己的条件也达到了,是不是就觉得自己应该得到那个职位?其实这时要问问为了这个职位我付出了什么,付出过没有,对于能够决定谁在这个职位的上司,我对他了解多少?和他关系如何?他需要什么样的人在这个职位上?我在这个职位上对他的好处和用处是什么?我有没有舍得花一些钱物等代价在这个职位上?如果什么都没做,很有可能是一个和别人睡了一觉的人上去了。自己没有得到,不能怨别人,别人也是付出代价的。

还有一点,任何生存,都是斗争得来的。如果没有保持高昂的斗志和进取精神,是很难生存的。当然,根据形势,有的时候处于战略守势,有的时候处于战略攻势,守时积蓄力量,攻时谋定后动,所谓庙算而后胜。用孙子的一句话说就是“胜兵先胜而后战,败兵先战而求胜”。

人世间本来就是灰色的,因为介于天堂的白和地狱的黑之间,在这灰色的人世,只能以灰色的规则和态度来处事,人能看到和运用的不过是眼前的一个灰色色度,往上不会是白,往下也不会是黑,还是另一种不可把握的灰。

这灰色的人世间,不可将自己的身家性命交予他人,或托于鬼神。

  1. 自律是成功的一半
  2. 好的编码规范是写好程序的一半。
  3. 软件工程及是在各种约束之中找到最佳的平衡的一门科学。

1,分号

  • 不要在行尾加分号, 也不要用分号将两条命令放在同一行.
  • 基本不需要使用分号

2,行长度

  • 每行不超过80个字符,长的导入语句和注释中的URL除外
  • 不要使用反斜杠连接行.

Python会将 圆括号, 中括号和花括号中的行隐式的连接起来 , 你可以利用这个特点. 如果需要, 你可以在表达式外围增加一对额外的圆括号.

1
2
3
4
5
 foo_bar(self, width, height, color='black',design=None, x='foo',
emphasis=None, highlight=0)

if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):

如果一个文本字符串在一行放不下, 可以使用圆括号来实现隐式行连接:

1
2
x = ('This will build a very long long '
'long long long long long long string')

3,括号

  • 宁缺毋滥的使用括号

除非是用于实现行连接, 否则不要在返回语句或条件语句中使用括号. 不过在元组两边使用括号是可以的.

1
2
3
4
5
6
7
8
9
10
if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
return foo
for (x, y) in dict.items(): ...

4,缩进

  • 用4个空格来缩进代码

绝对不要用tab, 也不要tab和空格混用. 对于行连接的情况, 你应该要么垂直对齐换行的元素(见 行长度 部分的示例), 或者使用4空格的悬挂式缩进(这时第一行不应该有参数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Yes:   # Aligned with opening delimiter
foo = long_function_name(var_one, var_two,
var_three, var_four)

# Aligned with opening delimiter in a dictionary
foo = {
long_dictionary_key: value1 +
value2,
...
}

# 4-space hanging indent; nothing on first line
foo = long_function_name(
var_one, var_two, var_three,
var_four)

# 4-space hanging indent in a dictionary
foo = {
long_dictionary_key:
long_dictionary_value,
...
}

5,空行

  • 顶级定义之间空两行, 方法定义之间空一行

顶级定义之间空两行, 比如函数或者类定义. 方法定义, 类定义与第一个方法之间, 都应该空一行. 函数或方法中, 某些地方要是你觉得合适, 就空一行.

6,空格

  • 按照标准的排版规范来使用标点两边的空格
  1. 括号内不要有空格.
  2. 不要在逗号, 分号, 冒号前面加空格, 但应该在它们后面加(除了在行尾).
  3. 参数列表, 索引或切片的左括号前不应加空格.
  4. 在二元操作符两边都加上一个空格, 比如赋值(=), 比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布尔(and, or, not). 至于算术操作符两边的空格该如何使用, 需要你自己好好判断. 不过两侧务必要保持一致.
  5. 当’=’用于指示关键字参数或默认参数值时, 不要在其两侧使用空格.
  6. 不要用空格来垂直对齐多行间的标记, 因为这会成为维护的负担(适用于:, #, =等):
1
2
3
4
5
6
7
8
Yes:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned

dictionary = {
"foo": 1,
"long_name": 2,
}
1
2
3
4
5
6
7
8
No:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned

dictionary = {
"foo" : 1,
"long_name": 2,
}

7,Shebang

  • 大部分.py文件不必以#!作为文件的开始. 根据 PEP-394 , 程序的main文件应该以 #!/usr/bin/python2或者 #!/usr/bin/python3开始.

#!先用于帮助内核找到Python解释器, 但是在导入模块时, 将会被忽略. 因此只有被直接执行的文件中才有必要加入#!.

8,注释

  • 确保对模块, 函数, 方法和行内注释使用正确的风格

文档字符串

Python有一种独一无二的的注释方式: 使用文档字符串. 文档字符串是包, 模块, 类或函数里的第一个语句. 这些字符串可以通过对象的doc成员被自动提取, 并且被pydoc所用. (你可以在你的模块上运行pydoc试一把, 看看它长什么样). 我们对文档字符串的惯例是使用三重双引号”“”( PEP-257 ). 一个文档字符串应该这样组织: 首先是一行以句号, 问号或惊叹号结尾的概述(或者该文档字符串单纯只有一行). 接着是一个空行. 接着是文档字符串剩下的部分, 它应该与文档字符串的第一行的第一个引号对齐. 下面有更多文档字符串的格式化规范.

模块

每个文件应该包含一个许可样板. 根据项目使用的许可(例如, Apache 2.0, BSD, LGPL, GPL), 选择合适的样板.

函数

所指的函数,包括函数, 方法, 以及生成器.

一个函数必须要有文档字符串, 除非它满足以下条件:

  1. 外部不可见
  2. 非常短小
  3. 简单明了

文档字符串应该包含函数做什么, 以及输入和输出的详细描述. 通常, 不应该描述”怎么做”, 除非是一些复杂的算法. 文档字符串应该提供足够的信息, 当别人编写代码调用该函数时, 他不需要看一行代码, 只要看文档字符串就可以了. 对于复杂的代码, 在代码旁边加注释会比使用文档字符串更有意义.

关于函数的几个方面应该在特定的小节中进行描述记录, 这几个方面如下文所述. 每节应该以一个标题行开始. 标题行以冒号结尾. 除标题行外, 节的其他内容应被缩进2个空格.

  • Args:

列出每个参数的名字, 并在名字后使用一个冒号和一个空格, 分隔对该参数的描述.如果描述太长超过了单行80字符,使用2或者4个空格的悬挂缩进(与文件其他部分保持一致). 描述应该包括所需的类型和含义. 如果一个函数接受foo(可变长度参数列表)或者*bar (任意关键字参数), 应该详细列出foo和*bar.

  • Returns: (或者 Yields: 用于生成器)

描述返回值的类型和语义. 如果函数返回None, 这一部分可以省略.

  • Raises:

列出与接口有关的所有异常.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
"""Fetches rows from a Bigtable.

Retrieves rows pertaining to the given keys from the Table instance
represented by big_table. Silly things may happen if
other_silly_variable is not None.

Args:
big_table: An open Bigtable Table instance.
keys: A sequence of strings representing the key of each table row
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.

Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:

{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}

If a key from the keys argument is missing from the dictionary,
then that row was not found in the table.

Raises:
IOError: An error occurred accessing the bigtable.Table object.
"""
pass

应该在其定义下有一个用于描述该类的文档字符串. 如果你的类有公共属性(Attributes), 那么文档中应该有一个属性(Attributes)段. 并且应该遵守和函数参数相同的格式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SampleClass(object):
"""Summary of class here.

Longer class information....
Longer class information....

Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""

def __init__(self, likes_spam=False):
"""Inits SampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0

def public_method(self):
"""Performs operation blah."""

块注释和行注释

最需要写注释的是代码中那些技巧性的部分. 如果你在下次 代码审查 的时候必须解释一下, 那么你应该现在就给它写注释. 对于复杂的操作, 应该在其操作开始前写上若干行注释. 对于不是一目了然的代码, 应在其行尾添加注释.

1
2
3
4
5
6
# We use a weighted dictionary search to find out where i is in
# the array. We extrapolate position based on the largest num
# in the array and the array size and then do binary search to
# get the exact number.

if i & (i-1) == 0: # True if i is 0 or a power of 2.

为了提高可读性, 注释应该至少离开代码2个空格.

另一方面, 绝不要描述代码. 假设阅读代码的人比你更懂Python, 他只是不知道你的代码要做什么.

9,类

  • 如果一个类不继承自其它类, 就显式的从object继承. 嵌套类也一样
1
2
3
4
5
6
7
8
9
10
11
12
Yes: class SampleClass(object):
pass


class OuterClass(object):

class InnerClass(object):
pass


class ChildClass(ParentClass):
"""Explicitly inherits from another class already."""

10,字符串

即使参数都是字符串, 使用%操作符或者格式化方法格式化字符串. 不过也不能一概而论, 你需要在+和%之间好好判定.

1
2
3
4
5
Yes: x = a + b
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}!'.format(imperative, expletive)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
1
2
3
4
No: x = '%s%s' % (a, b)  # use + in this case
x = '{}{}'.format(a, b) # use + in this case
x = imperative + ', ' + expletive + '!'
x = 'name: ' + name + '; score: ' + str(n)

避免在循环中用+和+=操作符来累加字符串. 由于字符串是不可变的, 这样做会创建不必要的临时对象, 并且导致二次方而不是线性的运行时间. 作为替代方案, 你可以将每个子串加入列表, 然后在循环结束后用 .join 连接列表. (也可以将每个子串写入一个 cStringIO.StringIO 缓存中.)

1
2
3
4
5
Yes: items = ['<table>']
for last_name, first_name in employee_list:
items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)
1
2
3
4
No: employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'

在同一个文件中, 保持使用字符串引号的一致性. 使用单引号’或者双引号”之一用以引用字符串, 并在同一文件中沿用. 在字符串内可以使用另外一种引号, 以避免在字符串中使用. PyLint已经加入了这一检查.

1
2
3
4
Yes:
Python('Why are you hiding your eyes?')
Gollum("I'm scared of lint errors.")
Narrator('"Good!" thought a happy Python reviewer.')
1
2
3
4
No:
Python("Why are you hiding your eyes?")
Gollum('The lint. It burns. It burns us.')
Gollum("Always the great lint. Watching. Watching.")

文档字符串必须使用三重双引号”””

多行字符串 通常用隐式行连接更清晰, 因为多行字符串与程序其他部分的缩进方式不一致.

11,文件和sockets

  • 在文件和sockets结束时, 显式的关闭它.

除文件外, sockets或其他类似文件的对象在没有必要的情况下打开, 会有许多副作用, 例如:

  1. 它们可能会消耗有限的系统资源, 如文件描述符. 如果这些资源在使用后没有及时归还系统, 那么用于处理这些对象的代码会将资源消耗殆尽.
  2. 持有文件将会阻止对于文件的其他诸如移动、删除之类的操作.
  3. 仅仅是从逻辑上关闭文件和sockets, 那么它们仍然可能会被其共享的程序在无意中进行读或者写操作. 只有当它们真正被关闭后, 对于它们尝试进行读或者写操作将会抛出异常, 并使得问题快速显现出来.

而且, 假设当文件对象析构时, 文件和sockets会自动关闭, 试图将文件对象的生命周期和文件的状态绑定在一起的想法, 都是不现实的. 因为有如下原因:

  1. 没有任何方法可以确保运行环境会真正的执行文件的析构. 不同的Python实现采用不同的内存管理技术, 比如延时垃圾处理机制. 延时垃圾处理机制可能会导致对象生命周期被任意无限制的延长.
  2. 对于文件意外的引用,会导致对于文件的持有时间超出预期(比如对于异常的跟踪, 包含有全局变量等).

推荐使用 “with”语句 以管理文件:

1
2
3
with open("hello.txt") as hello_file:
for line in hello_file:
print line

对于不支持使用”with”语句的类似文件的对象,使用 contextlib.closing():

1
2
3
4
5
import contextlib

with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
for line in front_page:
print line

12,TODO注释

  • 为临时代码使用TODO注释, 它是一种短期解决方案. 不算完美, 但够好了.

TODO注释应该在所有开头处包含”TODO”字符串, 紧跟着是用括号括起来的你的名字, email地址或其它标识符. 然后是一个可选的冒号.
接着必须有一行注释, 解释要做什么. 主要目的是为了有一个统一的TODO格式, 这样添加注释的人就可以搜索到(并可以按需提供更多细节).
写了TODO注释并不保证写的人会亲自解决问题. 当你写了一个TODO, 请注上你的名字.

1
2
# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.

如果你的TODO是”将来做某事”的形式, 那么请确保你包含了一个指定的日期(“2009年11月解决”)或者一个特定的事件(“等到所有的客户都可以处理XML请求就移除这些代码”).

13,导入格式

  • 每个导入应该独占一行
1
2
Yes: import os
import sys
1
No:  import os, sys

导入总应该放在文件顶部, 位于模块注释和文档字符串之后, 模块全局变量和常量之前. 导入应该按照从最通用到最不通用的顺序分组:

  1. 标准库导入
  2. 第三方库导入
  3. 应用程序指定导入

每种分组中, 应该根据每个模块的完整包路径按字典序排序, 忽略大小写.

1
2
3
4
5
import foo
from foo import bar
from foo.bar import baz
from foo.bar import Quux
from Foob import ar

14,语句

  • 通常每个语句应该独占一行

不过, 如果测试结果与测试语句在一行放得下, 你也可以将它们放在同一行. 如果是if语句, 只有在没有else时才能这样做. 特别地, 绝不要对 try/except 这样做, 因为try和except不能放在同一行.

1
2
3
4
5
6
7
8
9
10
11
#Do No like that!!

if foo: bar(foo)
else: baz(foo)

try: bar(foo)
except ValueError: baz(foo)

try:
bar(foo)
except ValueError: baz(foo)

15,访问控制

在Python中, 对于琐碎又不太重要的访问函数, 应该直接使用公有变量来取代它们, 这样可以避免额外的函数调用开销. 当添加更多功能时, 你可以用属性(property)来保持语法的一致性.

另一方面, 如果访问更复杂, 或者变量的访问开销很显著, 那么你应该使用像 get_foo()set_foo() 这样的函数调用. 如果之前的代码行为允许通过属性(property)访问 , 那么就不要将新的访问函数与属性绑定. 这样, 任何试图通过老方法访问变量的代码就没法运行, 使用者也就会意识到复杂性发生了变化.

16,命名

  • module_name, package_name, ClassName, method_name, ExceptionName, function_name, GLOBAL_VAR_NAME, instance_var_name, function_parameter_name, local_var_name.

应该避免的名称

  1. 单字符名称, 除了计数器和迭代器.
  2. 包/模块名中的连字符(-)
  3. 双下划线开头并结尾的名称(Python保留, 例如__init__

命名约定

  1. 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的.
  2. 用单下划线(_)开头表示模块变量或函数是protected的(使用from module import *时不会包含).
  3. 用双下划线(__)开头的实例变量或方法表示类内私有.
  4. 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.
  5. 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格), 但是模块名应该用小写加下划线的方式(如lower_with_under.py). 尽管已经有很多现存的模块使用类似于CapWords.py这样的命名, 但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰.

Python之父Guido推荐的规范

Type Public Internal
Modules lower_with_under _lower_with_under
Packages lower_with_under
Classes CapWords _CapWords
Exceptions CapWords
Functions lower_with_under() _lower_with_under()
Global/Class Constants CAPS_WITH_UNDER _CAPS_WITH_UNDER
Global/Class Variables lower_with_under _lower_with_under
Instance Variables lower_with_under _lower_with_under (protected) or __lower_with_under (private)
Method Names lower_with_under() _lower_with_under() (protected) or __lower_with_under() (private)
Function/Method Parameters lower_with_under
Local Variables lower_with_under

17,Main

即使是一个打算被用作脚本的文件, 也应该是可导入的. 并且简单的导入不应该导致这个脚本的主功能(main functionality)被执行, 这是一种副作用. 主功能应该放在一个main()函数中.

在Python中, pydoc以及单元测试要求模块必须是可导入的. 你的代码应该在执行主程序前总是检查 if __name__ == '__main__' , 这样当模块被导入时主程序就不会被执行.

1
2
3
4
5
def main():
...

if __name__ == '__main__':
main()

所有的顶级代码在模块导入时都会被执行. 要小心不要去调用函数, 创建对象, 或者执行那些不应该在使用pydoc时执行的操作.

19,参考链接

  1. Python风格规范
  2. Python 编码规范(Google)
  3. Python PEP8 编码规范中文版

安徐正静

1、pytest 特点

pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:

  • 简单灵活,容易上手
  • 支持参数化
  • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
  • pytest具有丰富第三方插件,良好的自定义扩展
  • 测试用例的skip和xfail处理
  • 可以很好的和jenkins集成
  • report框架—-allure 也支持了pytest

2、支持插件

完整的插件list,可以到下面这三个站点看看:

  1. https://docs.pytest.org/en/latest/plugins.html

  2. https://pypi.python.org

  3. https://github.com/pytest-dev

下面是一些出名的插件list:

  • pytest-repeat: 可以多次运行测试用例,用来提高发现那些偶然错误的几率
  • pytest-xdist: 可以利用机器的多核,提升测试的速度
  • pytest-timeout: 可以为测试加入超时
  • pytest-instatfail: 在错误发生的时候,立即报告它
  • pytest-sugar: 整合了pytest-instatfail以及代码高亮,颜色字体…
  • pytest-emoji: 为测试报告加入了一些有趣的东西
  • pytest-html: 在测试完成后,会生成一份html报告文件
  • pytest-pycodestyle, pytest-pep8, pytest-flake8: 进行代码规范检查
  • pytest-rerunfailures(失败case重复执行)
  • pytest-selenium
  • pytest-django
  • pytest-flask

3、安装

1
pip install -U pytest

验证安装的版本:

1
pytest --version

例子

1
2
3
4
5
6
7
import pytest

# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5

命令行切换到文件所在目录,执行测试(也可以直接在IDE中运行)

1
pytest  xxx.py

当需要编写多个测试样例的时候,我们可以将其放到一个测试类当中,如

1
2
3
4
5
6
7
8
class TestClass:  
def test_one(self):
x = "this"
assert 'h' in x

def test_two(self):
x = "hello"
assert hasattr(x, 'check')

4、如何编写pytest测试样例

规则:

  • 测试文件以test_开头(以_test结尾也可以)
  • 测试类以Test开头,并且不能带有 init 方法
  • 测试函数以test_开头
  • 断言使用基本的assert即可

5、运行模式

   Pytest的多种运行模式,让测试和调试变得更加得心应手,下面介绍5种常用的模式。在介绍之前需要提醒一句,运行pytest时会找当前目录及其子目录中的所有test_*.py 或 *_test.py格式的文件以及以test开头的方法或者class,不然就会提示找不到可以运行的case了。

5.1、运行后生成测试报告

运行后生成测试报告(htmlReport

安装pytest-html:

1
pip install -U pytest-html

运行模式:

1
pytest --html=report.html

报告效果:

img

在以上报告中可以清晰的看到测试结果和错误原因,定位问题很容易。

5.2、运行指定的case

  当我们写了较多的cases时,如果每次都要全部运行一遍,无疑是很浪费时间的,通过指定case来运行就很方便了。

例子代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TestClassOne(object):
def test_one(self):
x = "this"
assert 't'in x

def test_two(self):
x = "hello"
assert hasattr(x, 'check')


class TestClassTwo(object):
def test_one(self):
x = "iphone"
assert 'p'in x

def test_two(self):
x = "apple"
assert hasattr(x, 'check')

运行模式:

模式1:直接运行test_se.py文件中的所有cases:

1
pytest test_se.py

模式2:运行test_se.py文件中的TestClassOne这个class下的两个cases:

1
pytest test_se.py::TestClassOne

模式3:运行test_se.py文件中的TestClassTwo这个class下的test_one:

1
pytest test_se.py::TestClassTwo::test_one

注意:定义class时,需要以T开头,不然pytest是不会去运行该class的。

5.3、多进程运行cases

  当cases量很多时,运行时间也会变的很长,如果想缩短脚本运行的时长,就可以用多进程来运行。

安装pytest-xdist:

1
pip install -U pytest-xdist

运行模式:

1
pytest test_se.py -n NUM

其中NUM填写并发的进程数。

5.4、重试运行cases

  在做接口测试时,有事会遇到503或短时的网络波动,导致case运行失败,而这并非是我们期望的结果,此时可以就可以通过重试运行cases的方式来解决。

安装pytest-rerunfailures:

1
pip install -U pytest-rerunfailures

运行模式:

1
pytest test_se.py --reruns NUM

NUM填写重试的次数。

5.5、显示print内容

  在运行测试脚本时,为了调试或打印一些内容,我们会在代码中加一些print内容,但是在运行pytest时,这些内容不会显示出来。如果带上-s,就可以显示了。

运行模式:

1
pytest test_se.py -s

  另外,pytest的多种运行模式是可以叠加执行的,比如说,你想同时运行4个进程,又想打印出print的内容。可以用:

1
pytest test_se.py -s -n 4

以用‘-s’参数或者 ‘–capture=no’,这样就可以输出所有测试用的print信息 ,但是并不是实时显示而是等程序运行结束时一起显示。

5.6 使用log模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
import logging

logging.basicConfig(level=logging.DEBUG)

def test_1():
log = logging.getLogger('test_1')
time.sleep(1)
log.debug('after 1 sec')
time.sleep(1)
log.debug('after 2 sec')
time.sleep(1)
log.debug('after 3 sec')
assert 1, 'should pass'


def test_2():
log = logging.getLogger('test_2')
time.sleep(1)
log.debug('after 1 sec')
time.sleep(1)
log.debug('after 2 sec')
time.sleep(1)
log.debug('after 3 sec')
assert 0, 'failing for demo purposes'

pytest用logging和–capture=no实现实时输出log信息

1
pytest -s slowTest_logging.py

6,扩展阅读

  1. 全功能Python测试框架:pytest

  2. Full pytest documentation

  3. Pytest学习笔记

  4. pytest单元测试框架

–>

CaptureRequest

捕获请求,可以为不同的场景(预览 拍照)创建不同的请求,配置不同的属性,如:预览分辨率、预览目标、对焦模式、曝光模式等。

  • 包含捕获硬件(sensor、镜头、闪光灯等)、管道、控制算法、输出buffer,发送图像到的目标的配置。

  • 通过 CameraDevice 对象的 createCaptureRequest() 方法得到一个 CaptureRequest.Builder 对象,基本配置都是通过该构造者来配置;最后通过 CaptureRequest.Builder 对象的 build() 方法便可得到CaptureRequest 实例。

  • CaptureRequest 通过CameraCaptureSession的capture 或setRepeatingRequest方法,发送给camera device 捕获图像。

  • CaptureRequest 继承了Parcelable 接口,支持序列化。

内部类

CaptureRequest.Builder

典型的建造者模式,是 CaptureRequest 的构建者。

使用CameraDevice.createCaptureRequest(int)方法获取一个 CaptureRequest.Builder对象。其中的 int 取值为:(定义在CameraDevice中)

  • TEMPLATE_PREVIEW : 用于创建一个相机预览请求。相机会优先保证高帧率而不是高画质。适用于所有相机设备。
  • TEMPLATE_STILL_CAPTURE : 用于创建一个拍照请求。相机会优先保证高画质而不是高帧率。适用于所有相机设备。
  • TEMPLATE_RECORD : 用于创建一个录像请求。相机会使用标准帧率,并设置录像级别的画质。适用于所有相机设备。
  • TEMPLATE_VIDEO_SNAPSHOT : 用于创建一个录像时拍照的请求。相机会尽可能的保证照片质量的同时不破坏正在录制的视频质量。适用于硬件支持级别高于 LEGACY 的相机设备。
  • EMPLATE_ZERO_SHUTTER_LAG : 用于创建一个零延迟拍照的请求。相机会尽可能的保证照片质量的同时不损失预览图像的帧率,3A(自动曝光、自动聚焦、自动白平衡)都为 auto 模式。只适用于支持PRIVATE_REPROCESSING 和 YUV_REPROCESSING 的相机设备。
  • TEMPLATE_MANUAL : 用于创建一个手动控制相机参数的请求。相机所有自动控制将被禁用,后期处理参数为预览质量,手动控制参数被设置为合适的默认值,需要用户自己根据需求来调整各参数。适用于支持MANUAL_SENSOR 的相机设备。

支持的方法

1、addTarget

添加一个请求的输出surface,注意这个surface必须包含在 CameraDevice.createCaptureSession() 方法设置的输出surface集合中

2、removeTarget

移除指定的输出surface

3、CaptureRequest build()

使用当前配置构建一个CaptureRequest对象

4、T get(Key key)

CaptureRequest.Builder 的属性字段查询。这些字段定义了相机的具体配置。

5、set(Key key, T value)

设置指定key的值

6、void setTag(Object tag)

为该请求设置一个标签

支持的方法

1、T get(Key key)

和 CaptureRequest.Builder 中的 get 方法效果是一样的。

2、List<Key<?>> getKeys()

返回映射中包含的所有 Key 的列表。

3、Object getTag()

检索此请求的标签,如果有的话。对应 CaptureRequest.Builder 中的 setTag() 方法。

4、boolean isReprocess()

判断这是否是一个再处理的请求。

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void captureStillPicture() {
try {
final Activity activity = getActivity();
if (null == activity || null == mCameraDevice) {
return;
}
// 1. 先拿到一个 CaptureRequest.Builder 对象
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
// 2. 通过 CaptureRequest.Builder 对象设置一些捕捉请求的配置
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
setAutoFlash(captureBuilder);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
CameraCaptureSession.CaptureCallback CaptureCallback
= new CameraCaptureSession.CaptureCallback() {

@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
// start preview
}
};
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
// 3. 通过 CaptureRequest.Builder 对象的 `build()` 方法构建一个 CaptureRequest 对象
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

CaptureResult

CaptureResult 表示捕捉的结果,是从图像传感器捕获单个图像的结果的子集。包含捕获硬件(传感器、镜头、闪光灯)、处理管道、控制算法和输出缓冲区的最终配置的子集。

捕获结果由camera在对CaptureRequest进行处理后产生。还可以对捕获结果查询为捕获请求列出的所有属性,以确定捕获使用的最终值。结果还包括捕获过程中相机设备状态的附加元数据。
CaptureResult 对象也是不可变的。常使用的子类是 TotalCaptureResult

内部类

只有一个 CaptureResult.Key<T> 的内部类,用于 CaptureResult 类的字段查找。

类比于 CameraCharacteristics.KeyCaptureRequest.Key

支持的方法

1、T get(Key key)

获取 CaptureResult 中指定 key 的值,key 为 CaptureResult 类中的那些静态常量。

2、long getFrameNumber()

获取该结果申请的帧的id。

3、List<Key<?>> getKeys()

返回映射中包含的所有 Key 的列表。

4、CaptureRequest getRequest()

返回这个结果对应的 CaptureRequest 对象。

5、int getSequenceId()

获取发生故障时的序列ID。

扩展阅读

源码中system/media/camera/docs 存放了上文 提到的camera device key值及对应属性的解释相关文档。

CameraManager

1、概述

CameraManager 是一个负责查询和建立相机连接的系统服务,关键功能:

  1. 将相机信息封装到 CameraCharacteristics 中,并提获取 CameraCharacteristics 实例的方式。
  2. 根据指定的相机 ID 连接相机设备。
  3. 提供将闪光灯设置成手电筒模式的快捷方式。

2、获取实例

通过 Context 类的 getSystemService() 方法来获取一个系统服务

1
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);

3、内部类

CameraManager.AvailabilityCallback

相机设备的可用状态发生变化时,触发回调。

  • public void onCameraAvailable(@NonNull String cameraId)
  • public void onCameraUnavailable(@NonNull String cameraId)

String cameraId 相机设备的唯一标识。

CameraManager.TorchCallback

闪光灯的可用状态发生变化时触发回调。

  • public void onTorchModeUnavailable(@NonNull String cameraId)
  • public void onTorchModeChanged(@NonNull String cameraId, boolean enabled)
    • String cameraId 相机设备的唯一标识。
    • boolean enabled 闪光灯变化前的状态

CameraManager.CameraManagerGlobal

全局Camera管理实例,单例,保持一个与camera service得连接,同时分发API注册得可用回调。

4、主要接口

获取Camera设备列表

1
2
3
4
@NonNull
public String[] getCameraIdList() throws CameraAccessException {
return CameraManagerGlobal.get().getCameraIdList();
}

注册可用状态变化回调

注册一个回调用来当可用状态变化的时候,进行通知。

  • 注册一个相同的回调,那么新的会替代旧的。
  • 第一次注册回调时,立刻激活一次回调,回报当前可用的camera设备;
  • 不管什么时候调用了camera被打开了都会触发回调;
  • 如果没有必要时,记得注销回调。不然会占用资源。 回调将独立于一般Activity的生命周期,独立调用CameraManger。
  • 支持再给定的Handle或Executor 上触发回调;

支持的接口

1
2
3
4
5
public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback,
@Nullable Handler handler) {
CameraManagerGlobal.get().registerAvailabilityCallback(callback,
CameraDeviceImpl.checkAndWrapHandler(handler));
}
  • @NonNull AvailabilityCallback callback 新的回调

  • @Nullable Handler handler 在之上会调用callback,如果设置为null,则使用当前线程(android.os.Looper looper);

1
2
3
4
5
6
7
public void registerAvailabilityCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull AvailabilityCallback callback) {
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor);
}
  • @NonNull @CallbackExecutor Executor executor, 在之上调用callback。
  • @NonNull AvailabilityCallback callback 新的回调。

注销可用状态回调

移除之前注册的回调,此callback 不会再接收连接和断开的事件。

1
2
3
public void unregisterAvailabilityCallback(@NonNull AvailabilityCallback callback) {
CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
}

注册闪光灯回调

注册回调用于关注闪光灯状态

  • 注册一个相同的回调,那么新的会替代旧的。

  • 第一次注册时,会立刻回调具备闪光灯单元的所有Camera设备。

  • 注册此回调到camera service后,记得不需要的时候注销此回调,不然一旦闪光灯状态变化会触发回调,影响相应资源释放。回调将独立于一般Activity的生命周期,独立调用CameraManger。

  • 支持再给定的Handle或Executor 上触发回调。

支持的接口

1
2
3
4
public void registerTorchCallback(@NonNull TorchCallback callback, @Nullable Handler handler) {
CameraManagerGlobal.get().registerTorchCallback(callback,
CameraDeviceImpl.checkAndWrapHandler(handler));
}
  • @NonNull TorchCallback callback 新的回调

  • @Nullable Handler handler 在之上会调用callback,如果设置为null,则使用当前线程(android.os.Looper looper);

1
2
3
4
5
6
7
public void registerTorchCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull TorchCallback callback) {
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
CameraManagerGlobal.get().registerTorchCallback(callback, executor);
}
  • @NonNull @CallbackExecutor Executor executor, 在之上调用callback。
  • @NonNull TorchCallback callback 新的回调。

注销闪光灯回调

移除之前注册的回调,此callback 不会再接收闪光灯状态变化的事件。

1
2
3
public void unregisterTorchCallback(@NonNull TorchCallback callback) {
CameraManagerGlobal.get().unregisterTorchCallback(callback);
}

获取Camera特性

查询并获取指定Camera的特性和能力,这些特性是不可修改的。

  • 从API 29 开始,此接口也可用于查询物理摄像头的特性,物理摄像头仅可以是逻辑复合摄像头的一部分,不能直接被openCamera接口打开。
1
2
3
@NonNull
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
throws CameraAccessException
  • @NonNull String cameraId 可以是一个独立的camera 或者仅是一个物理摄像头。

打开Camera

建立一个到给定Camera的连接。

  • 及时打开的是使用 getCameraIdList 获取的cameraId指定的Camera 也可能操作失败,因为Camera有可能已经断开连接或者被其他更高级别的API打开而被占用。
  • 即使低优先级的client已经打开了Camera,高优先级的client也可以成功打开Camera,此时低级别的Client会收到回调事件android.hardware.camera2.CameraDevice.StateCallback#onDisconnected。如果你的client处于 顶层或者前台的Activity 那么你的client可以获得更高的优先级。
  • 一旦成功打开Camera,会触发回调 CameraDevice.StateCallback#onOpened,那么你就可以使用Camera的相关操作,比如发起拍照请求等。
  • 如果在初始化期间Camera断开,那么会有一个CameraDevice.StateCallback#onDisconnected回调其中带有Camera状态,回调CameraDevice.StateCallback#onOpened就会被跳过。
  • 如果打开Camera失败,会触发回调CameraDevice.StateCallback#onError onError,随后在这个Camera上的调用就会抛出异常CameraAccessException。

支持的接口

1
2
3
4
5
@RequiresPermission(android.Manifest.permission.CAMERA)
public void openCamera(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback,
@Nullable Handler handler)
throws CameraAccessException
  • @NonNull String cameraId: camera 唯一标识
  • CameraDevice.StateCallback callback CameraDevice状态回调
  • @Nullable Handler handler 在其上运行回调。
1
2
3
4
5
@RequiresPermission(android.Manifest.permission.CAMERA)
public void openCamera(@NonNull String cameraId,
@NonNull @CallbackExecutor Executor executor,
@NonNull final CameraDevice.StateCallback callback)
throws CameraAccessException
  • @NonNull String cameraId: camera 唯一标识
  • @NonNull @CallbackExecutor Executor executor CameraDevice状态回调
  • @Nullable Handler handler 在其上运行回调。
1
2
3
4
5
public void openCameraForUid(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback,
@NonNull Executor executor,
int clientUid)
throws CameraAccessException
  • 此接口为隐藏接口

  • int clientUid UID为clientUid 的应用将打开Camera,一般设置为USE_CALLING_UID,除非是可信任的服务。

设置闪光灯模式

设置指定camera得闪光灯模式,不需要打开camera。

  • 使用getCameraIdList获取可用Camera列表,使用getCameraCharacteristics查看Camera是否包含闪光灯。即使Camera包含闪光灯,也有可能打开闪光灯失败,因为或许在使用中。

  • setTorchMode调用成功,CameraManager.TorchCallback#onTorchModeChanged会触发。即使打开了闪光灯,应用也不是独占闪光灯或者Camera。如果最后一个打开闪光灯得应用退出了,闪光灯将关闭。

1
2
3
4
5
6
7
public void setTorchMode(@NonNull String cameraId, boolean enabled)
throws CameraAccessException {
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
}
CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
}

5、核心实现

相关代码位于源码位置:

1
2
3
4
5
frameworks\base\core\java\android\hardware\camera2\CameraManager.java
./frameworks/av/camera/aidl/android/hardware/ICameraServiceListener.aidl
./frameworks/av/camera/aidl/android/hardware/ICameraServiceProxy.aidl
./frameworks/av/camera/aidl/android/hardware/ICameraService.aidl
。。。
  • Application framework:用于给APP提供访问hardware的Camera API2,通过binder来访问camera service。

  • AIDL: 基于Binder实现的一个用于让App framework代码访问natice 代码的接口。其实现存在于下述路径:frameworks/av/camera/aidl/android/hardware。其中:

    • ICameraService 是相机服务的接口。用于请求连接、添加监听等。
    • ICameraDeviceUser 是已打开的特定相机设备的接口。应用框架可通过它访问具体设备。
    • ICameraServiceListener 和 ICameraDeviceCallbacks 分别是从 CameraService 和 CameraDevice 到应用框架的回调。
  • Natice framework:frameworks/av/。提供了ICameraService、ICameraDeviceUser、ICameraDeviceCallbacks、ICameraServiceListener等aidl接口的实现。以及camera server的main函数。

  • Binder IPC interface:提供进程间通信的接口,APP和CameraService的通信、CameraService和HAL的通信。其中,AIDL、HIDL都是基于Binder实现的。

  • Camera Service:frameworks/av/services/camera/。同APP、HAL交互的服务,起到了承上启下的作用。

  • HAL:Google的HAL定义了可以让Camera Service访问的标准接口。对于供应商而言,必须要实现这些接口。

查看CameraManager类,其主要功能由其内部类 CameraManagerGlobal 代理实现。CameraManagerGlobal 是一个单例,CameraManagerGlobal 是真正的实现层,它与 CameraService 创建连接,从而创建相机的连路。

0、引言

  • 从 Android 5.0 开始,Google 引入了一套全新的相机框架 Camera2(android.hardware.camera2)

  • 并废弃了旧的相机框架 Camera1(android.hardware.Camera)

1、Pipeline

Camera2 的 API 模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了 Pipeline 的工作流程,

为了解释上面的示意图,假设我们想要同时拍摄两张不同尺寸的图片,并且在拍摄的过程中闪光灯必须亮起来。整个拍摄流程如下:

  1. 创建一个用于从 Pipeline 获取图片的 CaptureRequest。
  2. 修改 CaptureRequest 的闪光灯配置,让闪光灯在拍照过程中亮起来。
  3. 创建两个不同尺寸的 Surface 用于接收图片数据,并且将它们添加到 CaptureRequest 中。
  4. 发送配置好的 CaptureRequest 到 Pipeline 中等待它返回拍照结果。

一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture 操作。最后我们从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。

2、Supported Hardware Level

相机功能的强大与否和硬件息息相关,不同厂商对 Camera2 的支持程度也不同,所以 Camera2 定义了一个叫做 Supported Hardware Level 的重要概念,其作用是将不同设备上的 Camera2 根据功能的支持情况划分成多个不同级别以便开发者能够大概了解当前设备上 Camera2 的支持情况。截止到 Android P 为止,从低到高一共有 LEGACY、LIMITED、FULL 和 LEVEL_3 四个级别:

  1. LEGACY:向后兼容的级别,处于该级别的设备意味着它只支持 Camera1 的功能,不具备任何 Camera2 高级特性。
  2. LIMITED:除了支持 Camera1 的基础功能之外,还支持部分 Camera2 高级特性的级别。
  3. FULL:支持所有 Camera2 的高级特性。
  4. LEVEL_3:新增更多 Camera2 高级特性,例如 YUV 数据的后处理等。

3、Capture

相机的所有操作和参数配置最终都是服务于图像捕获,例如对焦是为了让某一个区域的图像更加清晰,调节曝光补偿是为了调节图像的亮度。因此,在 Camera2 里面所有的相机操作和参数配置都被抽象成 Capture(捕获),所以不要简单的把 Capture 直接理解成是拍照,因为 Capture 操作可能仅仅是为了让预览画面更清晰而进行对焦而已。如果你熟悉 Camera1,那你可能会问 setFlashMode() 在哪?setFocusMode() 在哪?takePicture() 在哪?告诉你,它们都是通过 Capture 来实现的。

Capture 从执行方式上又被细分为【单次模式】、【多次模式】和【重复模式】三种,我们来一一解释下:

  • 单次模式(One-shot):指的是只执行一次的 Capture 操作,例如设置闪光灯模式、对焦模式和拍一张照片等。多个一次性模式的 Capture 会进入队列按顺序执行。
  • 多次模式(Burst):指的是连续多次执行指定的 Capture 操作,该模式和多次执行单次模式的最大区别是连续多次 Capture 期间不允许插入其他任何 Capture 操作,例如连续拍摄 100 张照片,在拍摄这 100 张照片期间任何新的 Capture 请求都会排队等待,直到拍完 100 张照片。多组多次模式的 Capture 会进入队列按顺序执行。
  • 重复模式(Repeating):指的是不断重复执行指定的 Capture 操作,当有其他模式的 Capture 提交时会暂停该模式,转而执行其他被模式的 Capture,当其他模式的 Capture 执行完毕后又会自动恢复继续执行该模式的 Capture,例如显示预览画面就是不断 Capture 获取每一帧画面。该模式的 Capture 是全局唯一的,也就是新提交的重复模式 Capture 会覆盖旧的重复模式 Capture。

4、CameraManager

CameraManager 是一个负责查询和建立相机连接的系统服务,它的功能不多,这里列出几个 CameraManager 的关键功能:

  1. 将相机信息封装到 CameraCharacteristics 中,并提获取 CameraCharacteristics 实例的方式。
  2. 根据指定的相机 ID 连接相机设备。
  3. 提供将闪光灯设置成手电筒模式的快捷方式。

5、CameraCharacteristics

CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括

  • 代表相机朝向的 LENS_FACING
  • 判断闪光灯是否可用的 FLASH_INFO_AVAILABLE
  • 获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等等。

如果你对 Camera1 比较熟悉,那么 CameraCharacteristics 有点像 Camera1 的 Camera.CameraInfo 或者 Camera.Parameters

6、CameraDevice

CameraDevice 代表当前连接的相机设备,它的职责有以下四个:

  1. 根据指定的参数创建 CameraCaptureSession。
  2. 根据指定的模板创建 CaptureRequest。
  3. 关闭相机设备。
  4. 监听相机设备的状态,例如断开连接、开启成功和开启失败等。

熟悉 Camera1 的人可能会说 CameraDevice 就是 Camera1 的 Camera 类,实则不是,Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的 CameraCaptureSession。

7、Surface

Surface 是一块用于填充图像数据的内存空间,例如你可以使用 SurfaceView 的 Surface 接收每一帧预览数据用于显示预览画面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 数据。每一个 Surface 都可以有自己的尺寸和数据格式,你可以从 CameraCharacteristics 获取某一个数据格式支持的尺寸列表。

8、CameraCaptureSession

CameraCaptureSession 实际上就是配置了目标 Surface 的 Pipeline 实例,我们在使用相机功能之前必须先创建 CameraCaptureSession 实例。一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。

9、CaptureRequest

CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。

10、CaptureResult

CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。

11、一些只有 Camera2 才支持的高级特性

如果要我给出强有力的理由解释为什么要使用 Camera2,那么通过 Camera2 提供的高级特性可以构建出更加高质量的相机应用程序应该是最佳理由了。

  1. 在开启相机之前检查相机信息
    出于某些原因,你可能需要先检查相机信息再决定是否开启相机,例如检查闪光灯是否可用。在 Caemra1 上,你无法在开机相机之前检查详细的相机信息,因为这些信息都是通过一个已经开启的相机实例提供的。在 Camera2 上,我们有了和相机实例完全剥离的 CameraCharacteristics 实例专门提供相机信息,所以我们可以在不开启相机的前提下检查几乎所有的相机信息。
  2. 在不开启预览的情况下拍照
    在 Camera1 上,开启预览是一个很重要的环节,因为只有在开启预览之后才能进行拍照,因此即使显示预览画面与实际业务需求相违背的时候,你也不得不开启预览。而 Camera2 则不强制要求你必须先开启预览才能拍照。
  3. 一次拍摄多张不同格式和尺寸的图片
    在 Camera1 上,一次只能拍摄一张图片,更不同谈多张不同格式和尺寸的图片了。而 Camera2 则支持一次拍摄多张图片,甚至是多张格式和尺寸都不同的图片。例如你可以同时拍摄一张 1440x1080 的 JPEG 图片和一张全尺寸的 RAW 图片。
  4. 控制曝光时间
    在暗环境下拍照的时候,如果能够适当延长曝光时间,就可以让图像画面的亮度得到提高。在 Camera2 上,你可以在规定的曝光时长范围内配置拍照的曝光时间,从而实现拍摄长曝光图片,你甚至可以延长每一帧预览画面的曝光时间让整个预览画面在暗环境下也能保证一定的亮度。而在 Camera1 上你只能 YY 一下。
  5. 连拍
    连拍 30 张图片这样的功能在 Camera2 出现之前恐怕只有系统相机才能做到了(通过 OpenGL 截取预览画面的做法除外),也可能是出于这个原因,市面上的第三方相机无一例外都不支持连拍。有了 Camera2,你完全可以让你的相机应用程序支持连拍功能,甚至是连续拍 30 张使用不同曝光时间的图片。
  6. 灵活的 3A 控制
    3A(AF、AE、AWB)的控制在 Camera2 上得到了最大化的放权,应用层可以根据业务需求灵活配置 3A 流程并且实时获取 3A 状态,而 Camera1 在 3A 的控制和监控方面提供的接口则要少了很多。例如你可以在拍照前进行 AE 操作,并且监听本这次拍照是否点亮闪光灯。

12、一些从 Camera1 迁移到 Camera2 的建议

如果你熟悉 Camera1,并且打算从 Camera1 迁移到 Camera2 的话,希望以下几个建议可以对你起到帮助:

  1. Camera1 严格区分了预览和拍照两个流程,而 Camera2 则把这两个流程都抽象成了 Capture 行为,只不过一个是不断重复的 Capture,一个是一次性的 Capture 而已,所以建议你不要带着过多的 Camera1 思维使用 Camera2,避免因为思维上的束缚而无法充分利用 Camera2 灵活的 API。
  2. 如同 Camera1 一样,Camera2 的一些 API 调用也会耗时,所以建议你使用独立的线程执行所有的相机操作,尽量避免直接在主线程调用 Camera2 的 API,HandlerThread 是一个不错的选择。
  3. Camera2 所有的相机操作都可以注册相关的回调接口,然后在不同的回调方法里写业务逻辑,这可能会让你的代码因为不够线性而错综复杂,建议你可以尝试使用子线程的阻塞方式来尽可能地保证代码的线性执行(熟悉 Dart 的人一定很喜欢它的 async 和 await 操作)。例如在子线程阻塞等待 CaptureResult,然后继续执行后续的操作,而不是将代码拆分到到 CaptureCallback.onCaptureCompleted() 方法里。
  4. 你可以认为 Camera1 是 Camera2 的一个子集,也就是说 Camera1 能做的事情 Camera2 一定能做,反过来则不一定行得通。
  5. 如果你的应用程序需要同时兼容 Camera1 和 Camera2,个人建议分开维护,因为 Camera1 蹩脚的 API 设计很可能让 Camera2 灵活的 API 无法得到充分的发挥,另外将两个设计上完全不兼容的东西搅和在一起带来的痛苦可能远大于其带来便利性,多写一些冗余的代码也许还更开心。
  6. 官方说 Camera2 的性能会更好,这句话听听就好,起码在较早期的一些机器上运行 Camera2 的性能并没有比 Camera1 好。
  7. 当设备的 Supported Hardware Level 低于 FULL 的时候,建议还是使用 Camera1,因为 FULL 级别以下的 Camera2 能提供的功能几乎和 Camera1 一样,所以倒不如选择更加稳定的 Camera1。

13、结束语

本章到此结束,主要是介绍了 Camera2 的一些基础概念,让大家能够基本了解 Camera2 的工作流程和基础概念,并且知道使用 Camera2 能够做些什么。如果你对 Camera2 还是感到很陌生,不要紧,后续的教程会带领大家逐步深入了解 Camera2。

参考链接

  1. Android Camera2 教程 · 第一章 · 概览

Camera架构

Camera的架构与Android系统的整体架构保持一致,如下图所示:

分层

  • Framework:frameworks/base/core/java/android/hardware/Camera.java

  • Android Runtime:frameworks/base/core/jni/android_hardware_Camera.cpp

  • C/C++ Libraries:

    • Client:
      frameworks/av/camera/CameraBase.cpp
      frameworks/av/camera/Camera.cpp
      frameworks/av/camera/ICamera.cpp
      frameworks/av/camera/aidl/android/hardware/ICamera.aidl
      frameworks/av/camera/aidl/android/hardware/ICameraClient.aidl
    • Server:
      frameworks/av/camera/cameraserver/main_cameraserver.cpp
      frameworks/av/services/camera/libcameraservice/CameraService.cpp
      frameworks/av/services/camera/libcameraservice/api1/CameraClient.cpp
      frameworks/av/camera/aidl/android/hardware/ICameraService.aidl
  • HAL:

    • HAL 1:
      frameworks/av/services/camera/libcameraservice/device1/CameraHardwareInterface.h
    • HAL 3:(主要学习了 HAL 1 的机制,HAL 3 以后再补充)
      frameworks/av/services/camera/libcameraservice/device3/***

C/S架构

Android Camera 框架是 一个 client/service 的架构。

  • service进程

属于服务端,由native c/c++代码实现,主要负责 通过HAL层和linux kernel中的camera driver交互。搜集camera driver 传递过来的数据,响应client端的请求

  • client进程

属于客户端,由java和native c/c++ 代码实现。可以看成应用程序。调用关系大致如下:

app –>camera2 api framework –> android_hardware_camera.cpp –>libcamera_client.so

目标

本系列的目标是从应用层 -> framework ->HAL –>driver 详细的分析学习Android Camera系统。

勿在浮沙筑高台

本文的目标是掌握清楚 智能指针 shared_ptr 、unique_ptr 和weak_ptr 的用法, 涉及到的概念和理念。

概念

RAII(Resource Acquisition Is Initialization)

RAII 含义即是资源分配即初始化。

  • 讲的是这样一个理念,将资源的管理放在一个类中,利用类的生命周期构造函数和析构函数来进行资源的管理和释放。用局部对象来表示资源。

  • 这是c++ 编程中的最重要编程技法之一。

  • 智能指针便是利用 RAII 的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现却像一个指针

C++11 中提供了三种智能指针

分别是 shared_ptr , unique_ptr 和 weak_ptr 。

  1. shared_ptr 允许多个指针指向同一个对象,
  2. unique_ptr 则“独占”所指向的对象,
  3. weak_ptr 则是和share_ptr 相辅相成的伴随类

shared_ptr

share_ptr是一个类,它产生的是一个类对象,而不是一个原生的指针对象,但是为了减少类对象与针对对象使用的差异性,所以share_ptr类故意重载了两种常见的指针操作符: *和->。从而share_ptr与普通指针使用方式一样。简言之,就是share_ptr生成的一个包含类型指针容器对象,它封装了指针对象,对指针对象负全责,包括生成、释放等;

  1. shared_ptr多个智能指针可以指向相同对象;
  2. 能够自动释放所指向的对象
  3. 该对象和其相关资源会在“最后一个引用被销毁”时候释放。
  4. 对象创建完就应该直接交给智能指针管理;
  5. shared_ptr 采用引用计数器,多个shared_ptr种的 T*ptr 指向同一内存区域(同一对象),并共同维护同一个引用计数器。
  6. 初始化指针并将引用计数置为1;
  7. 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数。
  8. 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数加1。调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
  9. share_ptr完美支持标准容器,并且不需要担心资源泄漏。而标准容易在使用指针对象时需要特别的小心,对指针需要额外的管理 。
  10. 特别需要注意的是,share_ptr的转型函数不能使用c++常用的转型函数,即static_cast,dynamic_cast,const_cast,而要使用static_pointer_cast,dynamic_pointer_cast,const_pointer_cast。原因有两个:static_cast,dynamic_cast,const_cast的功能是转换成对应的模版类型,即static_cast<T*>其实是转换成类型为T的指针;前面说了share_ptr生成的一个包含类型指针容器对象,使用简单的c++转型函数是将share_ptr对象转型为模版指针对象,这完全违背了使用share_ptr的初衷(除非你确确实实有这种需要!),导致转型的模版指针对象不能采用share_ptr进行管理。因为上面的两个原因:share_ptr为了支持转型,所以提供了类似的转型函数即static_pointer_cast,从而使转型后仍然为shared_pointer对象,仍然对指针进行管理;

Member functions

  • 构造函数 不谈拷贝构造的话,shared_ptr的基本构造方式有4种:

    • 无参数构造。
    • 传入一个指针构造一个shared_ptr
    • 传入一个指针和一个删除器构造一个shared_ptr。
    • 传入一个指针、一个删除器以及一个allocator构造一个shared_ptr。
    • 当然还有一些其他的,例如从auto_ptr从weak_ptr从null_ptr构造。
  • 析构函数 用来释放智能指针,至于是否释放智能指针所管理的对象,取决于成员use_count的值;

    • 如果use_count > 1,则引用计数器减一;
    • 如果use_count=1,则引用计数器置零,资源释放。
    • 如果use_count=0,则说明此智能指针本来就没有指向对象。
  • operator=

    赋值操作后,赋值前原来的 _refCount要自减 , 计数器_refCount要做++ ;

  • swap

    交换,使两个智能指针分别指向对方的对象。

  • reset 复位操作,不再指向对象,原来所指向对象的_refCount要自减

  • get 获取对象的原始指针。

  • operator* 相当于*get()

  • operator-> 可以像原指针一样,指向原指针的成员。

  • use_count 返回当前所指对象的引用计数。

  • unique 检查Usecount() == 1

  • [operator bool](http://www.cplusplus.com/reference/memory/shared_ptr/operator bool/) 检查 get()!=NULL

  • owner_before

    Owner-based ordering (public member function template )

Non-member functions

多线程问题

shared_ptr的线程安全的定义在boost的文档中有明确的说明:

  • 一个shared_ptr对象可以被多个线程同时read

  • 两个shared_ptr对象,指向同一个raw指针,两个线程分别write这两个shared_ptr对象,是安全的。包括析构。

  • 多个线程如果要对同一个shared_ptr对象读写,是线程不安全的

也就是说,唯一需要注意的就是:多个线程中对同一个shared_ptr对象读写时需要加锁。但是即使是加锁也有技巧。比较好的方式是:

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
std::shared_ptr<int> p1;
std::shared_ptr<int> p2 (nullptr);
std::shared_ptr<int> p3 (new int);
std::shared_ptr<int> p4 (new int, std::default_delete<int>());
std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());
std::shared_ptr<int> p6 (p5);
std::shared_ptr<int> p7 (std::move(p6));
std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));
std::shared_ptr<C> obj (new C);
std::shared_ptr<int> p9 (obj, obj->data);

std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n';
std::cout << "p2: " << p2.use_count() << '\n';
std::cout << "p3: " << p3.use_count() << '\n';
std::cout << "p4: " << p4.use_count() << '\n';
std::cout << "p5: " << p5.use_count() << '\n';
std::cout << "p6: " << p6.use_count() << '\n';
std::cout << "p7: " << p7.use_count() << '\n';
std::cout << "p8: " << p8.use_count() << '\n';
std::cout << "p9: " << p9.use_count() << '\n';

std::shared_ptr<int> foo = std::make_shared<int> (10);
// same as:
std::shared_ptr<int> foo2 (new int(10));

auto bar = std::make_shared<int> (20);

auto baz = std::make_shared<std::pair<int,int>> (30,40);

std::cout << "*foo: " << *foo << '\n';
std::cout << "*bar: " << *bar << '\n';
std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n'
return 0;
}

std::weak_ptr

引入是为了解决 std::share_ptr 的链接环问题。c++

一般结合强智能指针使用,它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段;weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 不会引起引用记数的增加或减少。

std::unique_ptr

unique_ptr 不共享它的指针。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。只能移动

手册

  1. http://www.cplusplus.com/reference/memory/shared_ptr/
  2. http://c.biancheng.net/view/430.html

后续

智能指针shared_ptr的实现

主要的问题

  1. shared_ptr 整体是如何实现的;
  2. shared_ptr 计数器的实现;
  3. shared_ptr 怎么实现-> 操作,访问所指向对象的成员就像访问本身的成员一样。