SegmentFault 姜家志最新的文章
2020-02-01T17:40:53+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
gitlab-runner升级到最新版本
https://segmentfault.com/a/1190000021669054
2020-02-01T17:40:53+08:00
2020-02-01T17:40:53+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>之前的文章介绍了<a href="https://link.segmentfault.com/?enc=BFffVRYjABSc%2Bm0YwWK68g%3D%3D.GTxuCgl03MR0Ddxvo6PI6YO2MQA7TIIcunCkQkLlf4Hm8BGyyB8neR7FA%2BkTAwo6" rel="nofollow">GitLab Runner安装</a>,在Ubuntu上默认直接安装了gitlab-runner,命令为:</p>
<pre><code>sudo apt install gitlab-runner</code></pre>
<p>但其实安装的版本为<code>10.5.0</code>,使用命令<code>gitlab-runner -v</code>可以查看,在运行docker executor出现错误:</p>
<pre><code> Gitlab CI Build failed gitlab-runner-prebuilt.tar.xz: no such file or directory</code></pre>
<p>出现这个问题的原因是因为gitlab-runner的版本过低,当前gitlab-runner最新版本为:12.7.1<br>解决办法:升级gitlab-runner为最新版本,先升级包:</p>
<pre><code># For Debian/Ubuntu/Mint
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
# For RHEL/CentOS/Fedora
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash</code></pre>
<p>需要先安装curl。<br>再安装最近版本的gitlab-runner:</p>
<pre><code>apt update
apt upgrade
# or
apt install gitlab-runner</code></pre>
<p>查看当前gitlab-runner版本:</p>
<pre><code>gitlab-runner -v
Version: 12.7.1
Git revision: 003fe500
Git branch: 12-7-stable
GO version: go1.13.5
Built: 2020-01-23T09:08:54+0000
OS/Arch: linux/amd64</code></pre>
<hr>
<p>本文由 <code>Matrixport</code> 姜家志 编写,转载无需授权<br><code>Matrixport</code>是一站式数字资产金融服务平台,致力于打造下一代数字金融平台,为加密经济而生</p>
使用Gradle对Java代码进行开发规范检查
https://segmentfault.com/a/1190000020087265
2019-08-15T21:56:27+08:00
2019-08-15T21:56:27+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p><a href="#">PMD</a><br>)是一种开源分析源代码错误的工具,它会发现一些常见的编程缺陷,比如未使用的变量,空的catch块,不必要的对象创建等。它支持Java,JavaScript等。<br>此外,用户还可以自己定义规则,检查Java代码是否符合某些特定的编码规范。<br>基于PMD,阿里巴巴基于自己的<a>Java编码规范</a>实现了<a>P3C-PMD</a></p>
<h3>设置检查规则</h3>
<p>检查规则为xml格式,注意配置中指定的配置文件在jra包中,需要p3c包编译到项目中才行正确引入:</p>
<pre><code>dependencies {
pmd "com.alibaba.p3c:p3c-pmd:2.0.0"
}</code></pre>
<p>xml配置文件如下:</p>
<pre><code class="xml"><?xml version="1.0"?>
<ruleset name="Custom ruleset"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>
自定义Rule set
</description>
<!-- 引入PMD制定的Rule, 来源于https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/rulesets/java -->
<rule ref="rulesets/java/android.xml">
<exclude name="CallSuperLast"/>
</rule>
<rule ref="rulesets/java/basic.xml">
<exclude name="CollapsibleIfStatements"/>
</rule>
<rule ref="rulesets/java/clone.xml"/>
<rule ref="rulesets/java/finalizers.xml"/>
<rule ref="rulesets/java/imports.xml"/>
<rule ref="rulesets/java/javabeans.xml"/>
<rule ref="rulesets/java/optimizations.xml">
<exclude name="LocalVariableCouldBeFinal"/>
<exclude name="MethodArgumentCouldBeFinal"/>
</rule>
<rule ref="rulesets/java/sunsecure.xml"/>
<rule ref="rulesets/java/unnecessary.xml">
<exclude name="UselessParentheses"/>
</rule>
<!-- 引入阿里的Rule, 来源于 alibaba/p3c -->
<rule ref="rulesets/java/ali-comment.xml">
</rule>
<rule ref="rulesets/java/ali-concurrent.xml">
</rule>
<rule ref="rulesets/java/ali-constant.xml">
</rule>
<rule ref="rulesets/java/ali-exception.xml">
</rule>
<rule ref="rulesets/java/ali-flowcontrol.xml">
</rule>
<rule ref="rulesets/java/ali-naming.xml">
</rule>
<rule ref="rulesets/java/ali-oop.xml">
</rule>
<rule ref="rulesets/java/ali-orm.xml">
</rule>
<rule ref="rulesets/java/ali-other.xml">
</rule>
<rule ref="rulesets/java/ali-set.xml">
</rule>
</ruleset></code></pre>
<p>该配置文件放在<code>etc/pmd/relest.xml</code>下</p>
<h3>配置Gradle</h3>
<pre><code>apply plugin: "pmd"
pmd {
toolVersion = '6.17.0'
ignoreFailures = true
ruleSetConfig = resources.text.fromFile("etc/pmd/ruleset.xml")
}
dependencies {
pmd "com.alibaba.p3c:p3c-pmd:2.0.0"
...
}</code></pre>
<p>其中<code>ignoreFailures</code>如果设置为true表示规范检查即使是不通过<code>gradle check</code>也不会报错,设置为false时,代码规范检查必须通过才check的时候才不会报错。</p>
<h3>运行PMD</h3>
<p>可以通过命令:</p>
<blockquote>gradle check</blockquote>
<p>运行<code>pmdMain </code>,它会检查__src/main/java__下的代码,还会运行<code>pmdTest </code>,它会检查__src/main/test__下的代码。<br>也可以分别运行这两个命令:</p>
<pre><code>gradle pmdMain
gradle pmdTest</code></pre>
<p>运行之后的结果在目录<code> build/reports/pmd</code>中的main.html,test.html文件中</p>
<h3>参考</h3>
<p><a>基于Gradle使用阿里巴巴Java开发规约进行代码检查</a></p>
GitLab CI持续集成 - .gitlab-ci.yml
https://segmentfault.com/a/1190000018563971
2019-03-18T23:06:14+08:00
2019-03-18T23:06:14+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在之前的文章中介绍了:</p>
<p><a href="https://link.segmentfault.com/?enc=ijZUn9SNQ3SGQnX7a7Xc%2BA%3D%3D.kwp%2Ba2k0x0u1C5BfMGUNk1bzvxO0DxFrpqCxPF0xL7Cn739X%2F9G1cZKdqrXdwZ5r" rel="nofollow">GitLab CI持续集成 - GitLab Runner 安装与注册</a></p>
<p><a href="https://link.segmentfault.com/?enc=GTO2etPVvZ%2Fr5w6AU6Wh0g%3D%3D.k2g3JJ6aXvZ9EjF51w2EE4Lj4KNNxmjoELEAALfJMUpb3pEk2jyPmFeBMr6LXrPX" rel="nofollow">GitLab CI持续集成-GitLab Runner</a></p>
<p>配置好环境下一步可以正式开始使用GitLab CI进行项目集成,这里以Java项目为例,使用Gradle做为项目自动构建工具,使用Gradle工具做代码质量检查,详情参见<a href="https://link.segmentfault.com/?enc=LzRUSk8FjVrxnE7GCIfQBg%3D%3D.aAwCGwzSE%2FaD%2B%2FckDcVhACfEXWqDQg5gE%2BNnbAYU13j22074agJWENUsMZkbX6lg" rel="nofollow">使用Gradle做Java代码质量检查</a>。</p>
<h2>.gitlab-ci.yml</h2>
<p>Gitlab CI使用YAML文件(.gitlab-ci.yml)来管理项目配置。该文件存放于项目仓库的根目录,它定义该项目如何构建。</p>
<blockquote>YAML是一个可读性高,用来表达数据序列的格式。YAML参考了其他多种语言,包括:C语言、Python、Perl,并从XML,电子邮件的数据格式中获得灵感。<br>YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据作为中心,而不是以标记语言为重点,而用反向缩略语重命名。 --- 维基百科</blockquote>
<p>.gitlab-ci.yml文件定义了一系列带有约束说明的任务,这些任务都是以任务名开始要包含<code>script</code>部分,一个.gitlab-ci.yml的例子:</p>
<pre><code>
stages:
- unit-test
UnitTest:
stage: unit-test
tags:
- spring-sample
script:
- gradle test
- gradle jacocoTestReport
- gradle sonarqube
when: always
</code></pre>
<p>下面详细解释下脚本中字段的含义</p>
<h2>构建脚本解析</h2>
<p><code>stages</code>定义可以被调用的阶段,默认定义为build,test和deploy,执行顺序:</p>
<ol>
<li>相同stage的job可以平行执行。</li>
<li>下一个stage的job会在前一个stage的job成功后开发执行</li>
</ol>
<p>这里定义了一个stage:<code>unit-test</code>。<br>下面的<code>UnitTest</code>是定义的一个任务,这个任务在stage为<code>unit-test</code>时执行。<br><code>tags</code>表示他需要执行的gitlab-runner,这里在一个tag为<em>spring-sample</em>的tag中执行。<br><code>script</code>代表需要执行的脚本,该例子中执行的脚本包括三步:</p>
<ol>
<li>运行gradle测试</li>
<li>进行单元测试覆盖情况检查</li>
<li>运行sonarqube进行代码质量检查</li>
</ol>
<p><code>when</code>定义何时执行任务,可以是on_success,on_failure,always(每次代码更新都触发)或者manual(手动触发)。</p>
<p>另外,比较常用的字段还有:<br><code>before_script </code>用来定义所有job之前运行的命令,包括部署任务等,可以是一个数组或者是多行字符串<br><code>after_script </code>用来定义所有job之后运行的命令。它必须是一个数组或者多行字符串<br>例如:</p>
<pre><code>job:
before_script:
- execute this instead of global before script
script:
- my command
after_script:
- execute this after my script</code></pre>
<p><code>cache</code>用来指定需要缓存的文件或目录,例如:</p>
<pre><code>job1:
script: test
cache:
paths:
- binaries/
- .config</code></pre>
<p>更多字段可以参考官方文档。</p>
<h2>运行GitLab CI</h2>
<p>配置完成之后,当把代码push到版本库中时就可以在CI/CD中看到相关的jobs:<br><img src="/img/remote/1460000018563974" alt="gitlb-ci jobs" title="gitlb-ci jobs"></p>
<p>进入详情可以看到详细的项目构建信息,可以根据产生的日志跟踪错误原因,这里出错的原因是在gitlab-ruuner中没有安装gradle的环境,安装完gradle环境之后构建任务运行通过。<a href="https://link.segmentfault.com/?enc=yVmO%2BjjQ9l%2FzUH79dcqs6g%3D%3D.0eehQwheXVn808txinnMoaWctZghoMSPbKRs%2FbMsC45xHxpyBm49olju2nBfpB6V" rel="nofollow">Ubuntu 安装 Java JDK & Gradle</a></p>
<h2>引用</h2>
<p>YAML: <a href="https://link.segmentfault.com/?enc=DKk5hR46CRvuXwWfZqIBrA%3D%3D.ohuP7sWV0GRKW9oGkk8Nml7v2AXCgN68cL58YJV0yLqy4ZBJejaAy30cCaKlVbrb" rel="nofollow">https://zh.wikipedia.org/wiki...</a><br>GitLab CI/CD Pipeline Configuration Reference :<a href="https://link.segmentfault.com/?enc=4HQWX%2BKgzwLHER%2BJIkWG9g%3D%3D.zkYWrjJ2M9ekaKP%2Fx8PhgngrD%2B9xHjYyXIYzAz7C6HbLFiaM9IXMzWV76ojasn9s" rel="nofollow">https://docs.gitlab.com/ee/ci...</a><br>通过 .gitlab-ci.yml配置任务:<a href="https://link.segmentfault.com/?enc=GOM81%2BIYtb%2FFdD9UL65ABg%3D%3D.r1ao64XgskO9fSWkMbqAUMFW9jf5BlAJwYHLIvVC5qhvpO6K4ZbGwztm5jFNJlp97XPvy0h6TVX6Y9yIVNjIrFOvY9w2jeRrMTUi%2BCoZERc%3D" rel="nofollow">https://github.com/Fennay/git...</a></p>
Gitlab CI持续集成 - GitLab Runner 安装与注册
https://segmentfault.com/a/1190000018550849
2019-03-18T09:35:52+08:00
2019-03-18T09:35:52+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>前篇文章<a href="https://link.segmentfault.com/?enc=R%2Fe6GGU%2FtSawRWDnwZKDmg%3D%3D.MhqdAJCJZcU%2FYYkAnqmhM0k5zuMlVoho02z7Md10ZylgzICeVg17M4oCnnKqZf8%2F" rel="nofollow">GitLab CI持续集成-GitLab Runner</a>主要介绍了持续集成,以及GitLab CI持续集成的环境,这边文章主要介绍下GitLab Runner的安装以及使用。</p>
<h2>GitLab Runner安装</h2>
<p>需要添加gitlab官方库:</p>
<pre><code> # For Debian/Ubuntu/Mint
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
# For RHEL/CentOS/Fedora
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash</code></pre>
<p>通过命令安装:</p>
<pre><code># MacOS
sudo brew install gitlab-ci-multi-runner
# For Debian/Ubuntu/Mint
sudo apt-get install gitlab-ci-multi-runner
# For RHEL/CentOS/Fedora
sudo yum install gitlab-ci-multi-runner
</code></pre>
<h2>gitlab-runner 注册</h2>
<p>首先要先获取gitlab-ci的Token:</p>
<blockquote>项目主页 -> Sttings -> CI/CD -> Runners Expand</blockquote>
<p><img src="/img/remote/1460000018550852" alt="获取Token" title="获取Token"></p>
<p>使用命令注册gitlab-runner:</p>
<blockquote>gitlab-runner register</blockquote>
<p>需要按照步骤输入:</p>
<ol>
<li>输入gitlab的服务URL,这个使用的是<a href="https://link.segmentfault.com/?enc=AxApc5JSJLkbydeO7VI%2FIQ%3D%3D.WxgMPM48C57wtB6acvQZHtZfmR2zCkA3MEp0ar5gokQ%3D" rel="nofollow">https://gitlab.com/</a>
</li>
<li>输入gitlab-ci的Toekn,获取方式参考上图</li>
<li>关于集成服务中对于这个runner的描述</li>
<li>给这个gitlab-runner输入一个标记,这个tag非常重要,在后续的使用过程中需要使用这个tag来指定gitlab-runner</li>
<li>是否运行在没有tag的build上面。在配置gitlab-ci的时候,会有很多job,每个job可以通过tags属性来选择runner。这里为true表示如果job没有配置tags,也执行</li>
<li>是否锁定runner到当前项目</li>
<li>选择执行器,gitlab-runner实现了很多执行器,可用在不同场景中运行构建,详情可见<a href="https://link.segmentfault.com/?enc=CKhMEWs13np6XB0FEKXZUw%3D%3D.3BLOITxeGEIqUnj7bxRc1eAaw%2FW8UbnBrHHRBjdNCSCEOQuJrC7SnU5j%2Bz0Pv1k2pUul75%2BBs2xOH1v3B%2FsjFQ%3D%3D" rel="nofollow">GitLab Runner Executors</a>,这里选用Shell模式</li>
</ol>
<p>刷新页面就可以看到新增的一个Runner:<br><img src="/img/remote/1460000018550853" alt="gitlab-runner" title="gitlab-runner"></p>
<p>这个GitLabRunner就安装好了,下一步就是把项目集成到gitlab-ci中,开始持续集成了。</p>
<h2>引用</h2>
<p><a href="https://link.segmentfault.com/?enc=PGQfgcW8l7uNfXKXt%2BBIUQ%3D%3D.t89FR1rFrV2gYBDPviW1p6fT8KibVEHKuJFpJbXl7SM%3D" rel="nofollow">GitLab Runner Document</a><br><a href="https://link.segmentfault.com/?enc=nRqoCe21mF1kX6%2B0%2F7WjUg%3D%3D.jv2j26qeal9MCORVBb1gpj19P2hqbSZJrmCZHy81i56Ce%2Bi4oN%2F1jUOn3kBSMMWzqv5E0pEsoN70K29z0nX2rQ%3D%3D" rel="nofollow">GitLab Runner Executors</a></p>
GitLab CI持续集成-GitLab Runner
https://segmentfault.com/a/1190000018536862
2019-03-17T21:11:29+08:00
2019-03-17T21:11:29+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>GitLab CI是开源的持续集成服务,GitLab Runner是一个开源项目,用于运作任务,并把结果发送回GitLab,它与GitLab CI一起使用。</p>
<h2>持续集成</h2>
<blockquote>持续(Continuous integration ,缩写CI)是一种软件工程流程,是将所有软件工程师对于软件的工作副本持续集成到共享主线(mainline)的一种举措。该名称最早由Grady Booch 在他的布区方法中提出,不过他并不支持在一天中进行数次集成。之后举措成为极限编程驱动开发(TDD)的作法中,通常还会搭配自动单元测试。持续集成的提出主要是为解决软件进行系统集成时面临的各项问题。-维基百科</blockquote>
<p>持续集成一般包括一些流程:</p>
<blockquote>合并代码<br> 安装依赖<br>编译<br>测试<br>发布</blockquote>
<p>持续集成必须依靠以下原则:</p>
<ul>
<li>维护一个代码知识库</li>
<li>自动构建,通过一个单一指令来达成系统建构</li>
<li>一旦代码更改好,下一个阶段应该要进行所有的测试,以确保软件开发的成果匹配预期</li>
<li>减少冲突,一天至少提交一次</li>
<li>每次变更必须要快速完成,如此一来便可以避免集成问题</li>
<li>尽可能的缩小测试环境和正式环境的差距,服务虚拟化通常更容易实现这个目标</li>
<li>尽早集成</li>
<li>任何人都可以查看最后的建构的结果</li>
<li>自动部署</li>
</ul>
<p>持续集成可以快速发现错误,定位错误也比较容易,它的目的就是让产品可以快速迭代,同时还能保证高质量。核心措施代码集成到主干前,必须通过自动化测试。</p>
<h2>GitLab CI</h2>
<p><img src="/img/remote/1460000018536865" alt="GitLab CI流程" title="GitLab CI流程"></p>
<p>GitLab CI是为GitLab提供持续集成服务的一整套系统。在GitLab8.0以后的版本是默认集成了GitLab-CI并且默认启用的。<br>使用GitLab CI需要在仓库跟目录创建一个gitlab-ci.yml的文件,它用来指定持续集成需要运行的环境,以及要执行的脚本。还需要设置一个gitlab-runner,当有代码push变更的时候,gitlab-runner会自动开始pipeline,并在gitlab上显示持续集成的结果。</p>
<h2>GitLab Runner</h2>
<p>GitLab Runner是使用Go语言编写的,可以做为一个二进制文件运行,不需要特定的语言要求,他创建了一个持续集成的的环境,所需要的程序使用Docker来安装,配置好GitLab Runner运行的环境。GitLab Runner实际上都是docker container,由GitLab Runner来自动创建,运行的环境由GitLab Runner程序控制,使用docker来建立runner,使得每一个虚拟环境都干净,轻量,相互隔离,互不影响。<br>GitLab-Runner一般都是配合GitLab-CI使用的,在GitLab里面定义一个属于这个工程的软件集成脚本,用来自动化地完成一些软件集成工作。<br>GitLab-Runner执行情况如下:<br><img src="/img/remote/1460000018536866" alt="执行时序图" title="执行时序图"></p>
<ol>
<li>本地代码改动</li>
<li>变动代码推送到GitLab上</li>
<li>GitLab 将这个变动通知GitLab-CI</li>
<li>GitLab-CI找出这个工程相关联的gitlab-runner</li>
<li>gitlab-runner把代码更新到本地</li>
<li>根据预设置的条件配置好环境</li>
<li>根据预定义的脚本(一般是.gitlab-ci.yml)执行</li>
<li>把执行结果通知给GitLab</li>
<li>GitLab显示最终执行的结果</li>
</ol>
<p>gitlab-runner可以在不同的主机上部署,也可以在同一个主机上设置多个gitlab-runner ,还可以根据不同的环境设置不同的环境,比如我们需要区分研发环境,测试环境以及正式环境等。</p>
使用Gradle做Java代码质量检查
https://segmentfault.com/a/1190000018533109
2019-03-17T08:39:52+08:00
2019-03-17T08:39:52+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<h2>Maven --> Gradle</h2>
<p>首先安装gradle:<br>Mac安装</p>
<pre><code>brew install gradle </code></pre>
<p>Ubuntu安装</p>
<pre><code>apt install gradle</code></pre>
<p>Maven项目切换Gradle项目,再Maven根目录下运行:</p>
<pre><code>gradle init --type pom</code></pre>
<p>运行成功之后运行命令<code>gradle build</code>,成功之后删除pom.xml即可。</p>
<h2>使用jacoco分析单元测试</h2>
<p><a href="https://link.segmentfault.com/?enc=ZBjxM8ogV6msDkF6%2F43zCg%3D%3D.cJQfLf5SBZFvc6lJvels3SDsg%2BaMzDvoqR7xAgRG0%2BQTYbNUk8TR7%2B0ki%2BqkvIwb" rel="nofollow">jacoco</a>是一个分析单元测试覆盖率的工具,使用它运行单元测试后,可以给出代码中那些部分被单元测试到,哪些部分没有被单元测试覆盖,并且还会给出整个项目的单元测试覆盖情况。<br>在<code>build.gradle</code>中添加jacoco插件</p>
<pre><code>apply plugin: 'jacoco'</code></pre>
<p>运行命令进行单元测试分析</p>
<pre><code>gradle jacocoTestReport</code></pre>
<p>或者可以再Gradle的工具菜单中<code>Tasks -> other -> jacocoTsestReport</code>可以直接启动单元测试的分析。<br><img src="/img/remote/1460000018533112" alt="jacocoTestReport" title="jacocoTestReport"><br>在项目中build目录下可以看到<code>jacoco</code>目录,以及<code>reports/test/html</code>目录,后者主要是jacoco生成的报告。<br><img src="/img/remote/1460000018533113" alt="jacoco报告" title="jacoco报告"></p>
<h2>使用SonarQube做代码质量检查</h2>
<p><a href="https://link.segmentfault.com/?enc=nSF%2BbjxNinWW7RHnv8ZNNA%3D%3D.b%2BpTgyUUNBSs4JGWmNdPFUtZsPUDap0%2BxQVdWBBQve4eS1DHrqefjmooJcIHrj35" rel="nofollow">SonarQube</a>是一个开源的代码质量管理系统,支持超过25种编程语言,提供重复代码、编码标准、单元测试、单元测试覆盖率,代码复杂度,潜在Bug、注释和软件设计的报告等。<br>在gradle中使用SonarQube首先要添加依赖,在编译脚本中添加:</p>
<pre><code>buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6-rc1")
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.5.RELEASE")
}
}</code></pre>
<p>添加插件:</p>
<pre><code>apply plugin: "org.sonarqube"</code></pre>
<p>配置SonarQube:</p>
<pre><code>sonarqube {
properties {
property "sonar.sourceEncoding", "UTF-8"
property "sonar.host.url", "https://sonarcloud.io"
property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
property "sonar.login", "test"
property "sonar.password", "test"
}
}</code></pre>
<p>或者只使用token上传分析结果即可:</p>
<pre><code>property "sonar.login", "token"
</code></pre>
<p>SonarQube本身并没有提供单元测试覆盖率的工具,需要在使用jacoco的分析结果,在SonarQube中添加jacoco相关的配置</p>
<pre><code>
sonarqube {
properties {
property "sonar.jacoco.reportPath", "$rootDir/build/jacoco/test.exec"
property "sonar.jacoco.itReportPath", "$rootDir/build/jacoco/acceptanceTest.exec"
property "sonar.jacoco.excludes", "*/st/*"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.host.url", "https://sonarcloud.io"
property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
property "sonar.login", "test"
property "sonar.password", "test"
}
}</code></pre>
<p>运行命令<code>gradle sonarqube</code>即可对代码进行分析,并上传分析结果,因为SonarQube的分析依赖jacoco的单元测试分析,所以需要先运行命令<code>gradle jacocoTestReport</code>。最终需要运行的组合命令是:</p>
<pre><code>gradle jacocoTestReport
gradle sonarqube</code></pre>
Mac上Cargo编译错误: failed to run custom build command for '*.*'
https://segmentfault.com/a/1190000017869192
2019-01-12T22:48:20+08:00
2019-01-12T22:48:20+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
2
<p>编译Rust的项目时候出现了错误:</p>
<pre><code>error: failed to run custom build command for croaring-sys v0.3.7
process didn't exit successfully: /Users/.../grin/target/release/build/croaring-sys-20d6d5c35e3a436a/build-script-build (exit code: 101)
--- stdout
TARGET = Some("x86_64-apple-darwin")
OPT_LEVEL = Some("3")
HOST = Some("x86_64-apple-darwin")
CC_x86_64-apple-darwin = None
CC_x86_64_apple_darwin = None
HOST_CC = None
CC = None
CFLAGS_x86_64-apple-darwin = None
CFLAGS_x86_64_apple_darwin = None
HOST_CFLAGS = None
CFLAGS = None
DEBUG = Some("false")
running: "cc" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "-m64" "-Wall" "-Wextra" "-std=c11" "-march=native" "-O3" "-o" "/Users/.../grin/target/release/build/croaring-sys-4f7af44253f571e8/out/CRoaring/roaring.o" "-c" "CRoaring/roaring.c"</code></pre>
<p>关键的错误信息是:</p>
<pre><code>error: unknown type name 'uint64_t' cargo:warning= uint64_t ri resident_size;</code></pre>
<p>原因是升级了Mac系统之后 C++ .h 不正确造成的。<br>解决的方式,是删除clang相关的编译环境,并重新安装,首先删除头文件:</p>
<blockquote>rm -rf /usr/local/include/*</blockquote>
<p>再卸载LLVM相关的工具链</p>
<blockquote>brew uninstall llvm</blockquote>
<p>最后需要卸载掉Xcode命令行工具:</p>
<blockquote>rm -rf /Library/Developer/CommandLineTools</blockquote>
<p>卸载掉clang相关工具之后,再重新安装。<br>安装Xcode命令行工具:</p>
<blockquote>xcode-select --install</blockquote>
<p>安装llvm</p>
<blockquote>brew install --with-toolchain llvm</blockquote>
<p>重新编译正常。</p>
使用IntelliJ做为Rust IDE
https://segmentfault.com/a/1190000017782831
2019-01-06T16:50:06+08:00
2019-01-06T16:50:06+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
4
<p><code>Rust</code> 是一个由Mozilla主导开发的通用编译型编译语言。它的设计准则为"安全,并发,实用",支持函数式,并发式,过程式以及面向对象的编程风格。<br>IntelliJ 是最好的使用的Java IDE之一 ,它支持各种插件,其中<a href="https://link.segmentfault.com/?enc=0JLQDx8CTyFq6vgilZqLcQ%3D%3D.vqVMrwsBHj%2FpzJIdcMCnZaBdiJQGJl0eluky8GgrJuxpDUirJaZUqqV8pCzeaeCm" rel="nofollow">intellij-rust</a>就是<code>Rust</code>在IntelliJ上的插件,可以使用该插件在IntelliJ上面进行Rust开发,下面就简单介绍下如何安装Rust并使用IntelliJ做为其IDE。</p>
<h2>安装Rust</h2>
<p>安装Rust不要直接Rust语言本身,例如使用<code>brew install rust</code>就只是安装了rust语言本身而已,应该安装的是<code>rustup</code>,<code>rustup</code>是rust官方版本的管理工具,是安装rust的首选。它的主要特点是:</p>
<ol>
<li>管理Rust二进制文件</li>
<li>配置Rust工具链</li>
<li>管理Rust相关组件</li>
<li>只依赖bash,curl和常见的unix工具</li>
<li>支持多平台</li>
</ol>
<p>在使用Rust开发过程中常常是用到的工具有<code>rustc</code>,<code>rust-src</code>,<code>cargo</code>,这些都可以使用rustup进行管理。<br>其中<code>cargo</code>是Rust项目管理的工具,提供了一系列的工具,从项目的建立,构建到测试,运行到部署,都为Rust项目的管理提供尽可能完成的手段。<br><code>rustc</code>是rust语言的编译器。<br><code>rust-src</code>是rust标准库。</p>
<p>安装rustup:</p>
<blockquote>curl <a href="https://link.segmentfault.com/?enc=fE5sba7Ky9gC1B8ioBAVjw%3D%3D.BcdJP54MjXB9mlJMM%2BRLJisOlTnYxJUcIY5Trs1Ajyo%3D" rel="nofollow">https://sh.rustup.rs</a> -sSf | sh</blockquote>
<p>安装过程中会让选择安装方式,使用默认方式安装即可,默认安装<code>cargo</code>。安装之后需要设置两个目录到PATH变量中:</p>
<ul>
<li>$HOME/.cargo/bin,cargo的bin目录</li>
<li>$HOME/.cargo/env,为shell配置的目录</li>
</ul>
<p>通过<code>rustup help</code>可以看到rustup的相关命令,上述的默认按照并不包含组件<code>rust-src</code>的安装,需要单独安装组件<code>rust-src</code>:</p>
<blockquote>rustup component add rust-src</blockquote>
<p>这样Rust的环境安装都已经完成,在使用IntelliJ做为Rust的IDE中要用的组件包括:<code>rustc</code>,<code>cargo</code>和<code>rust-src</code>。</p>
<h2>安装IntelliJ插件</h2>
<p>需要安装两个插件 <code>intellij-rust</code>和<code>intellij-toml</code>, <code>intellij-rust</code>是Rust语言插件,<code>intellij-toml</code>是为Toml语言的插件,是为cargo的配置文件cargo.toml使用。<br>安装方式:<code>Perferences.. -> Plugins </code>在Marketplact中直接搜索Rust<br><img src="/img/remote/1460000017782834" alt="搜索rust" title="搜索rust"><br>同样方式搜索<code>toml</code>并安装。<br>安装完插件之后就可以新建一个项目选择Rust:<br><img src="/img/remote/1460000017782835" alt="新建rust项目" title="新建rust项目"><br>可以看到 Toolchain location 是配置的$HOME/.cargo/bin,而Standard library是之前安装的<code>rust-src</code>的目录。<br>创建项目成功可以看到一个完整的rust项目:<br><img src="/img/remote/1460000017782836" alt="rust项目结构" title="rust项目结构"></p>
<h5>引用</h5>
<ol>
<li>维基百科:<a href="https://link.segmentfault.com/?enc=UbW9H0RsL8J2gqtMKl%2FHnA%3D%3D.kgR8%2Fzl1HNPafMi%2FM175N2gaPviCX9jc7WF58ogXoO43A%2BdD6Kc4k6YOrU%2Fdc533" rel="nofollow">https://zh.wikipedia.org/wiki...</a>
</li>
<li>Rust lang: <a href="https://link.segmentfault.com/?enc=e5xGYazrco9SO9QuHidZQg%3D%3D.f9nUVigxLMqBQTlmFj9dk3gOyIeozr3CRhD4jjuGr%2B0%3D" rel="nofollow">https://www.rust-lang.org/</a>
</li>
<li>intellij-rust :<a href="https://link.segmentfault.com/?enc=RqCTvo55bhleamw8RnUG1Q%3D%3D.GsNz9reS6OSdpK%2BnyvpiMTPsu5IjbbIB0LaJdHP5w3t2OvPWcUty7SeFWa7c9veq" rel="nofollow">https://github.com/intellij-r...</a>
</li>
<li>intellij-tom :<a href="https://link.segmentfault.com/?enc=Vft0ON8skF5R4DDm8dOZGg%3D%3D.2gYKrOSqHQo2ErFfdiRbhQLmj%2BgZgiWL7GDNBUdOncEsfc%2FDKO6Oh%2FDqVka1bO55" rel="nofollow">https://github.com/intellij-r...</a>
</li>
</ol>
Bitcoin序列化库使用
https://segmentfault.com/a/1190000013014430
2018-01-27T13:32:20+08:00
2018-01-27T13:32:20+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>Bitcoin序列化功能主要实现在<code>serialize.h</code>文件,整个代码主要是围绕<code>stream</code>和参与序列化反序列化的类型<strong>T</strong>展开。 </p>
<p>stream这个模板形参表达具有<code>read(char**, size_t)</code> 和<code> write(char**, size_t) </code>方法的对象, 类似Golang 的io.reader ,io.writer。</p>
<p>简单的使用例子:</p>
<pre><code class="c++">#include <serialize.h>
#include <streams.h>
#include <hash.h>
#include <test/test_bitcoin.h>
#include <stdint.h>
#include <memory>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup)
struct student
{
std::string name;
double midterm, final;
std::vector<double> homework;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(name);
READWRITE(midterm);
READWRITE(final);
READWRITE(homework);
}
};
bool operator==(student const& lhs, student const& rhs){
return lhs.name == rhs.name && \
lhs.midterm == rhs.midterm && \
lhs.final == rhs.final && \
lhs.homework == rhs.homework;
}
std::ostream& operator<<(std::ostream& os, student const& st){
os << "name: " << st.name << '\n'
<< "midterm: " << st.midterm << '\n'
<< "final: " << st.final << '\n'
<< "homework: " ;
for (auto e : st.homework) {
os << e << ' ';
}
return os;
}
BOOST_AUTO_TEST_CASE(normal)
{
student s, t;
s.name = "john";
s.midterm = 77;
s.final = 82;
auto v = std::vector<double> {83, 50, 10, 88, 65};
s.homework = v;
CDataStream ss(SER_DISK, 0);
ss << s;
ss >> t;
BOOST_CHECK(t.name == "john");
BOOST_CHECK(t.midterm == 77);
BOOST_CHECK(t.final == 82);
BOOST_TEST(t.homework == v, boost::test_tools::per_element());
CDataStream sd(SER_DISK, 0);
CDataStream sn(SER_NETWORK, PROTOCOL_VERSION);
sd << s;
sn << s;
BOOST_CHECK(Hash(sd.begin(), sd.end()) == Hash(sn.begin(), sn.end()));
}
BOOST_AUTO_TEST_CASE(vector)
{
auto vs = std::vector<student>(3);
vs[0].name = "bob";
vs[0].midterm = 90;
vs[0].final = 76;
vs[0].homework = std::vector<double> {85, 53, 12, 75, 55};
vs[1].name = "jim";
vs[1].midterm = 96;
vs[1].final = 72;
vs[1].homework = std::vector<double> {91, 46, 19, 70, 59};
vs[2].name = "tom";
vs[2].midterm = 85;
vs[2].final = 57;
vs[2].homework = std::vector<double> {91, 77, 45, 50, 35};
CDataStream ss(SER_DISK, 0);
auto vt = std::vector<student>(3);
ss << vs;
ss >> vt;
BOOST_TEST(vs == vt, boost::test_tools::per_element());
}
BOOST_AUTO_TEST_CASE(unique_ptr){
auto hex = "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000";
CDataStream stream(ParseHex(hex), SER_NETWORK, PROTOCOL_VERSION);
//CTransaction tx(deserialize, stream);
auto utx = std::unique_ptr<const CTransaction>(nullptr);
::Unserialize(stream, utx);
BOOST_TEST(utx->vin.size() == std::size_t(1));
BOOST_TEST(utx->vout[0].nValue == 1000000);
}
BOOST_AUTO_TEST_SUITE_END()</code></pre>
<p>需要在用户的自定义类型内部 添加 <strong>ADD_SERIALIZE_METHODS</strong> 调用, 宏展开后:</p>
<pre><code class="c++">template<typename Stream> \
void Serialize(Stream& s) const { \
NCONST_PTR(this)->SerializationOp(s, CSerActionSerialize()); \
} \
template<typename Stream> \
void Unserialize(Stream& s) { \
SerializationOp(s, CSerActionUnserialize()); \
}
</code></pre>
<p>这个宏为用户自定义类型添加了两个成员函数: <strong>Serialize</strong> 和 <strong>Unserialize</strong>, 它们内部调用需要用户自定义的模板成员函数<strong>SerializationOp</strong> , 在 <strong>SerializationOp</strong> 函数内部, 主要使用 <strong>READWRITE</strong> 和 <strong>READWRITEMANY</strong> 宏,完成对自定义类型每个数据成员的序列化与反序列化。</p>
<pre><code class="c++">#define READWRITE(obj) (::SerReadWrite(s, (obj), ser_action))
#define READWRITEMANY(...) (::SerReadWriteMany(s, ser_action, __VA_ARGS__))
struct CSerActionSerialize
{
constexpr bool ForRead() const { return false; }
};
struct CSerActionUnserialize
{
constexpr bool ForRead() const { return true; }
};
template<typename Stream, typename T>
inline void SerReadWrite(Stream& s, const T& obj, CSerActionSerialize ser_action)
{
::Serialize(s, obj);
}
template<typename Stream, typename T>
inline void SerReadWrite(Stream& s, T& obj, CSerActionUnserialize ser_action)
{
::Unserialize(s, obj);
}
template<typename Stream, typename... Args>
inline void SerReadWriteMany(Stream& s, CSerActionSerialize ser_action, Args&&... args)
{
::SerializeMany(s, std::forward<Args>(args)...);
}
template<typename Stream, typename... Args>
inline void SerReadWriteMany(Stream& s, CSerActionUnserialize ser_action, Args&... args)
{
::UnserializeMany(s, args...);
}
</code></pre>
<p>需要在用户的自定义类型内部 添加 <strong><em><em>ADD_SERIALIZE_METHODS</em></em></strong> 调用, 宏展开后:</p>
<pre><code class="c++">
template<typename Stream> \
void Serialize(Stream& s) const { \
NCONST_PTR(this)->SerializationOp(s, CSerActionSerialize()); \
} \
template<typename Stream> \
void Unserialize(Stream& s) { \
SerializationOp(s, CSerActionUnserialize()); \
}
</code></pre>
<p>这个宏为用户自定义类型添加了两个成员函数: <code>Serialize</code> 和 <code>Unserialize</code>, 它们内部调用需要用户自定义的模板成员函数<code>SerializationOp</code> , 在 <code>SerializationOp</code> 函数内部, 主要使用 <code>READWRITE</code> 和 <code>READWRITEMANY</code> 宏,完成对自定义类型每个数据成员的序列化与反序列化。</p>
<pre><code class="c++">#define READWRITE(obj) (::SerReadWrite(s, (obj), ser_action))
#define READWRITEMANY(...) (::SerReadWriteMany(s, ser_action, __VA_ARGS__))
struct CSerActionSerialize
{
constexpr bool ForRead() const { return false; }
};
struct CSerActionUnserialize
{
constexpr bool ForRead() const { return true; }
};
template<typename Stream, typename T>
inline void SerReadWrite(Stream& s, const T& obj, CSerActionSerialize ser_action)
{
::Serialize(s, obj);
}
template<typename Stream, typename T>
inline void SerReadWrite(Stream& s, T& obj, CSerActionUnserialize ser_action)
{
::Unserialize(s, obj);
}
template<typename Stream, typename... Args>
inline void SerReadWriteMany(Stream& s, CSerActionSerialize ser_action, Args&&... args)
{
::SerializeMany(s, std::forward<Args>(args)...);
}
template<typename Stream, typename... Args>
inline void SerReadWriteMany(Stream& s, CSerActionUnserialize ser_action, Args&... args)
{
::UnserializeMany(s, args...);
}
</code></pre>
<p>这里SerReadWrite 和 SerReadWriteMany 各自有两个overload 实现, 区别是末尾分别传入了不同的类型<code>CSerActionSerialize</code> 和 <code>CSerActionUnserialize</code> , 而且 形参 ser_action 根本没有在内部使用, 查阅了相关资料, 这里使用了c++ 泛型编程常用的一种模式:</p>
<p><a href="https://link.segmentfault.com/?enc=dQ755cNoi65fEdVMAlIhAg%3D%3D.ukVHw%2BbYPnEKxxdf0F1xQr%2FSbKCBi%2FcbYsmZWpGMMRfMJNUNcZuoja%2ByPUwu53A4B4Eg2n71SOCGzXCq9Z1L7BsQMWBsCwMGFODTjloOD78%3D" rel="nofollow">tag dispatch 技术</a>](<a href="https://link.segmentfault.com/?enc=dHRWI3HVAm6LBbVyM%2FUtXw%3D%3D.q5OUfp0KQIZR1xC9NmpGCVEyQ1NcGiPE5wSzP7GTncysI2PJMt%2BBVHAFCf%2BidyA9aC3LbMuWmDAXxs10cDWerEFAUitc47rcI8dt9xDfxdE%3D" rel="nofollow">https://akrzemi1.wordpress.co...</a> 另一个解释:[<a href="https://link.segmentfault.com/?enc=oI6kFT0%2FTdUcp%2Foe%2F8IQBA%3D%3D.%2FRF%2FA2NxuhuSJJ2gXJjOutZk%2FM5zGxrTatAZony3YkWZOxHYmedV67LQLJ%2FUhdORJMyffQp6jL0y9Eujxv7bWg%3D%3D" rel="nofollow">https://arne-mertz.de/2016/10...</a>://arne-mertz.de/2016/10/tag-dispatch/),</p>
<p>通过携带不同的类型,在编译时选择不同的overload 实现, CSerActionSerialize 对应序列化的实现, CSerActionUnserialize 对应反序列化的实现。</p>
<p><code>SerializeMany</code> 和 <code>SerializeMany</code>是通过变长模板parameter pack 展开技术来实现, 以 <code>SerializeMany</code> 为例子:</p>
<pre><code class="c++">
template<typename Stream>
void SerializeMany(Stream& s)
{
}
template<typename Stream, typename Arg>
void SerializeMany(Stream& s, Arg&& arg)
{
::Serialize(s, std::forward<Arg>(arg));
}
template<typename Stream, typename Arg, typename... Args>
void SerializeMany(Stream& s, Arg&& arg, Args&&... args)
{
::Serialize(s, std::forward<Arg>(arg));
::SerializeMany(s, std::forward<Args>(args)...);
}
</code></pre>
<p><code>SerializeMany</code>有三个overload 实现,假设从上倒下,分别编号为1, 2, 3; 当我们传入两个以上的实参是,编译器选择版本3,版本3内部从parameter pack 弹出一个参数,然后传给版本2调用,剩下的参数列表,传给版本3,递归调用,直到parameter pack 为空时,选择版本1。</p>
<p>迂回这么长, 最终序列化真正使用全局名称空间的 <code>Serialize</code> 来完成, 反序列化通过调用<code>Unserialize</code>实现。</p>
<p>而 <code>Serialize</code> 和<code>Unserialize</code> 又有一堆的overload 实现, Bitcoin 作者实现一些常见类型的模板特化,比如,std::string, 主要设计表达脚本的prevector , std::vector, std::pair, std::map, std::set, std::unique_ptr, std::share_ptr 。 c++ 的模板匹配根据参数列表的匹配程度选择不同的实现, 优先精准匹配,最后选择类型T的成员函数实现:</p>
<pre><code class="c++">template<typename Stream, typename T>
inline void Serialize(Stream& os, const T& a)
{
a.Serialize(os);
}
template<typename Stream, typename T>
inline void Unserialize(Stream& is, T& a)
{
a.Unserialize(is);
}</code></pre>
<p>在序列化string, map, set, vector, prevector 等可能包含多元素的集合类型时, 内部会调用 <code>ReadCompactSize</code>和 <code>WriteCompactSize</code>读取写入紧凑编码的元素个数:</p>
<pre><code class="c++">template<typename Stream>
void WriteCompactSize(Stream& os, uint64_t nSize)
{
if (nSize < 253)
{
ser_writedata8(os, nSize);
}
else if (nSize <= std::numeric_limits<unsigned short>::max())
{
ser_writedata8(os, 253);
ser_writedata16(os, nSize);
}
else if (nSize <= std::numeric_limits<unsigned int>::max())
{
ser_writedata8(os, 254);
ser_writedata32(os, nSize);
}
else
{
ser_writedata8(os, 255);
ser_writedata64(os, nSize);
}
return;
}
template<typename Stream>
uint64_t ReadCompactSize(Stream& is)
{
uint8_t chSize = ser_readdata8(is);
uint64_t nSizeRet = 0;
if (chSize < 253)
{
nSizeRet = chSize;
}
else if (chSize == 253)
{
nSizeRet = ser_readdata16(is);
if (nSizeRet < 253)
throw std::ios_base::failure("non-canonical ReadCompactSize()");
}
else if (chSize == 254)
{
nSizeRet = ser_readdata32(is);
if (nSizeRet < 0x10000u)
throw std::ios_base::failure("non-canonical ReadCompactSize()");
}
else
{
nSizeRet = ser_readdata64(is);
if (nSizeRet < 0x100000000ULL)
throw std::ios_base::failure("non-canonical ReadCompactSize()");
}
if (nSizeRet > (uint64_t)MAX_SIZE)
throw std::ios_base::failure("ReadCompactSize(): size too large");
return nSizeRet;
}
</code></pre>
<p>针对位宽1,2,4,8的基础类型,<code>Serialize</code> 和 <code>Unserialize</code> 最终调用ser_writedata<em>, ser_readdata8</em> 完成实现。</p>
<pre><code class="c++">template<typename Stream> inline void Serialize(Stream& s, char a ) { ser_writedata8(s, a); } // TODO Get rid of bare char
template<typename Stream> inline void Serialize(Stream& s, int8_t a ) { ser_writedata8(s, a); }
template<typename Stream> inline void Serialize(Stream& s, uint8_t a ) { ser_writedata8(s, a); }
template<typename Stream> inline void Serialize(Stream& s, int16_t a ) { ser_writedata16(s, a); }
template<typename Stream> inline void Serialize(Stream& s, uint16_t a) { ser_writedata16(s, a); }
template<typename Stream> inline void Serialize(Stream& s, int32_t a ) { ser_writedata32(s, a); }
template<typename Stream> inline void Serialize(Stream& s, uint32_t a) { ser_writedata32(s, a); }
template<typename Stream> inline void Serialize(Stream& s, int64_t a ) { ser_writedata64(s, a); }
template<typename Stream> inline void Serialize(Stream& s, uint64_t a) { ser_writedata64(s, a); }
template<typename Stream> inline void Serialize(Stream& s, float a ) { ser_writedata32(s, ser_float_to_uint32(a)); }
template<typename Stream> inline void Serialize(Stream& s, double a ) { ser_writedata64(s, ser_double_to_uint64(a)); }
template<typename Stream> inline void Unserialize(Stream& s, char& a ) { a = ser_readdata8(s); } // TODO Get rid of bare char
template<typename Stream> inline void Unserialize(Stream& s, int8_t& a ) { a = ser_readdata8(s); }
template<typename Stream> inline void Unserialize(Stream& s, uint8_t& a ) { a = ser_readdata8(s); }
template<typename Stream> inline void Unserialize(Stream& s, int16_t& a ) { a = ser_readdata16(s); }
template<typename Stream> inline void Unserialize(Stream& s, uint16_t& a) { a = ser_readdata16(s); }
template<typename Stream> inline void Unserialize(Stream& s, int32_t& a ) { a = ser_readdata32(s); }
template<typename Stream> inline void Unserialize(Stream& s, uint32_t& a) { a = ser_readdata32(s); }
template<typename Stream> inline void Unserialize(Stream& s, int64_t& a ) { a = ser_readdata64(s); }
template<typename Stream> inline void Unserialize(Stream& s, uint64_t& a) { a = ser_readdata64(s); }
template<typename Stream> inline void Unserialize(Stream& s, float& a ) { a = ser_uint32_to_float(ser_readdata32(s)); }
template<typename Stream> inline void Unserialize(Stream& s, double& a ) { a = ser_uint64_to_double(ser_readdata64(s)); }
template<typename Stream> inline void Serialize(Stream& s, bool a) { char f=a; ser_writedata8(s, f); }
template<typename Stream> inline void Unserialize(Stream& s, bool& a) { char f=ser_readdata8(s); a=f; }
</code></pre>
<p>另外代码开始处的</p>
<pre><code class="c++">struct deserialize_type {};
constexpr deserialize_type deserialize {};</code></pre>
<p>作为tag 类型, tag 对象, 主要为多个实现签名有以下形式:</p>
<pre><code class="c++">template <typename Stream>
T::T(deserialize_type, Stream& s)</code></pre>
<p>的反序列化构造器做分发, 目前主要是CTransaction, CMutableTransaction 类型:</p>
<pre><code class="c++">template <typename Stream>
CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {}
template <typename Stream>
CMutableTransaction(deserialize_type, Stream& s) {
Unserialize(s);
}
</code></pre>
<p>原文地址: <a href="https://link.segmentfault.com/?enc=J7s19pr4eCtf67lN%2BPLmSQ%3D%3D.xoLzDFiXhukRk%2FTvNkXejEbEH2KrtZwb4ZmMNjWoCMpdbrHc8%2FpAoLXDjYdNPU9S%2F4Ni2NKJJ3cGiQjF0hO2nA%3D%3D" rel="nofollow">https://mp.weixin.qq.com/s/_fhGCfkI0sT3fasSKWqdPA</a></p>
<hr>
<p>本文由 <code>Copernicus团队 喻建</code>写作,转载无需授权。</p>
Bitcoin Cash 的链上交易数
https://segmentfault.com/a/1190000011973199
2017-11-12T20:39:55+08:00
2017-11-12T20:39:55+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>Bitcoin Cash 价格迎来了一波大涨,市值已经成为了第二大加密货币。<br><img src="/img/remote/1460000011973204?w=800&h=126" alt="btc-bch-eth" title="btc-bch-eth"></p>
<p>相对于价格的上涨,我更加的关心链上交易数的变化情况。</p>
<h2>LTC的链上交易</h2>
<p>前段时间国内交易所被关闭的时候,有一段时间比特儿和okcoin的BTC价格差很大,我有一个朋友告诉我说这可以搬砖呀!我说人民币渠道已经关闭了,你在两个交易所都没有人民币怎么搬砖呢?过了一天他说他搬砖赚了点钱,而且就是在比特儿和okcoin搬的。我问他是怎么做到的呢?他告诉我说使用莱特币搬的砖。<br><img src="/img/remote/1460000011973205?w=800&h=366" alt="btc ltc transactions" title="btc ltc transactions"><br>上图描述的是BTC和LTC的链上交易数的对比图,很长一段时间内LTC的链上交易数一直都很少,那个时候BTC也不拥堵,从2017开始,BTC开始出现拥堵了,可以看到这个时候LTC的链上交易数开始增加,而且在BTC每日处理数量最高的时候(也就是BTC最堵的时候)LTC的链上交易数都会出现一个小高峰。一方面是因为LTC的价格增长导致LTC的交易需求增加,从而导致LTC链上交易的增加。另一方面LTC在做链上价值转移功能。这部分的需求因为交易拥堵BTC无法完成,溢出的交易需求由LTC实现了。<br>自从Bitcoin Cash从8月1日分叉出来之后,LTC的链上交易数的增加和BTC的拥堵情况关联性不是那么明显了。这部分溢出的交易需求部分由BCH替代了。<br><img src="/img/remote/1460000011973206?w=800&h=346" alt="btc,bch,ltc 3m's transactions" title="btc,bch,ltc 3m's transactions"><br>上图是3个月内BTC、BCH、LTC的链上交易数的对比图,其中蓝色代表BTC,红色代表BCH,橘色代表LTC,从中我们可以看到,随着BCH的发展,LTC的链上交易数的增加和BTC的拥堵相关性越来越小。在LTC的没有剧烈的波动的情况下,LTC的链上交易数并没有随着BTC的拥堵而增加。<br><img src="/img/remote/1460000011973207?w=800&h=518" alt="LTC K-line" title="LTC K-line"><br>LTC9月份的时候有一次高价,之后的价格波动还算比较平稳,对照上图的9月份的链上交易数,LTC在价格高涨的时候链上的交易数会出现增加。很明显的可以看到之后LTC的链上交易数很平稳,价格也没有出现巨大的波动。而且随着BCH链上的交易数增加,LTC的链上交易数出现了减少。</p>
<p>链上交易的需求又有那些呢?</p>
<h2>搬砖</h2>
<blockquote><p>搬砖:就是在价格低的平台买入,然后转移到价格高的平台卖出。从国外交易所中特币单价最低的一个站,搬砖到国内的交易所价格高的卖掉,或者是做相反的操作,就可以获利。</p></blockquote>
<p>如果是跨国平台之间的搬砖需要用到本国的法币,这是一个很麻烦的事情,法币的外汇管制是非常严格的,外汇的兑换也是一件很有挑战的事情。而且法币的兑换成本也很高,而加密货币却不一样,他全球通用,而且转账成本低(BCH)。如果搬砖的换一种思路的话:搬砖的时候不赚法币的差价,赚bch或者ltc的差价呢?<br>比如A交易所的一个币种是0.1个BCH,而另一个交易所B中他的价格是0.2个BCH。那么我只要在A交易所买入,在B交易所卖出,就可以挣到一个0.1个BCH,搬砖就完成了。<br>能够作为搬砖的币种需要满足:</p>
<ol>
<li><p>低廉的转账手续费</p></li>
<li><p>确认速度要快</p></li>
<li><p>有巨大的交易</p></li>
<li><p>用户基数大</p></li>
<li><p>交易所支持</p></li>
</ol>
<p>不得不说LTC和BCH的确认时间还是有差距的,一个2.5分钟,一个是10分钟,但是只要在1到2个出块时间之内能够确认,一般来说这段时间内的价格波动不会特别巨大,还是可以接受的,举个例子来说,人民币的转账速度其实也不是那么快,搬砖的也是可以接受的。<br><img src="/img/remote/1460000011973208?w=400&h=130" alt="人行系统关闭的提示" title="人行系统关闭的提示"><br>在人行系统关闭的时候,是无法大额转账的,只能小额转账,而且本身转账法币之后交易所也需要时间确认的。对于确认时间的问题,搬砖的交易员们自己也会想办法对冲风险。确认时间我个人认为不超过2个小时是可以接受的,超过的话真的会影响搬砖了。低廉的交易手续费非常必要,搬个转挣了个200块钱,手续费收个100块,也是无法接受的事。</p>
<h2>买币</h2>
<p>交易所规避法币风险最好的办法就是不做法币兑换,只做币币交易,而个人想要买币就需要先获到一种加密货币,再到币币交易所买自己需要的币种。个人获取加密货币的方式在国内一般都是走OTC,BTC作为市值和用户基数最大的币种,当然也是OTC最大的市场。但BTC存在手续费太高和确认时间太长的问题,当然高手续费对于土豪是无所谓的,100多块钱的手续费是够我等屌丝2天的饭钱了。<br>BTC的交易摩擦成本太高了。<br>当前国内的交易所关闭,想要买币就会产生比较高的摩擦成本。如果你选择直接让人在美国或者香港代购,大概的摩擦成本是2%-7%, OTC市场买币也会产生摩擦成本,第一部分是OTC交易平台他们一般会收费0.3%左右,第二是OTC提供商,他有可能是矿主,自己卖币,也有可能是做OTC套利的,这个摩擦成本不是太好统计有多少了。</p>
<p>如果想买其他币种,目前可以先买BTC,再充值到币币交易所购买,但是BTC的转账手续费太高。一个BTC的价格现在是6100刀,而平均每笔交易的手续费是45到左右,摩擦成本是0.7%。<br><img src="/img/remote/1460000011973209?w=800&h=341" alt="fee of btc" title="fee of btc"></p>
<p>你要知道交易平台的收费才是0.3%左右,而转账的手续费就需要0.7%,当然说对于一次性买10个BTC的土豪来说这个摩擦成本会降低到0.07%,不是什么大事。这很明显也不是我等屌丝能做的事情,屌丝只能想其他办法了。</p>
<h2>Bitcoin Cash</h2>
<p>除了场外买BTC到币币交易平台购买其他币种之外,我们还有其他的选择吗?答案是显而易见的,BCH。<br>比如我在场外购买BCH,再充到币币交易平台,手续费只要几分钱(注:人民币)而已,但是BCH存在一个缺点,就是出块并不稳定,因为EDA(Emergency Difficulty Adjustment)的原因,BCH的出块速度时儿快,时儿慢。该缺陷已经引起了BCH开发团的足够重视,在11月13号的时候会进行一次硬分叉升级为DAA(Difficulty Adjustment Algorithm),BCH的出块会稳定在10分钟一个块,稳定的出块才能有更好的应用场景。<br>但是把BCH充到交易所之后却又了另一个问题,你还是需要换成BTC才能交易,因为其他币种都是以BTC计价的,这样的话交易流程就会变成为:</p>
<blockquote><p>BCH->BTC->我想要的币种</p></blockquote>
<p>这中间交易平台会收取两次交易手续费,建议交易平台能够增加以BCH为基础的交易对,这样的好处就是让用户能够快捷的购买自己需要的币种,为什么是BCH,不是ETH或者LTC呢?<br>ETH的交易主要的智能合约的交易,而且ETH也存在交易拥堵的问题。<br>LTC的交易量小,而且用户基础不大,比如:我以及我身边的朋友都没持有LTC。</p>
<h2>总结</h2>
<p>BCH已经吸收了BTC溢出的链上交易需求,而LTC没有了该需求的情况下,链上交易数在减少。经过DAA调整之后的BCH,出块更加的稳定,具备更加的链上交易能力,在BCH放量上涨的这几天中,做BCH OTC交易的平台或者个人是非常开心的,加密货币市场是一个自由公开的市场,有人从其中能够发掘很多的机会,满足用户的需求(减少摩擦成本)。我觉得很快就会有交易平台支持币币交易的,总有人对于用户需求是很敏感的,做出更好的产品的,自己的平台也会壮大起来。</p>
<p>打赏地址: 16uoPajbFeKcVXdwDSuGxb7unYy1X1rMss<br><img src="/img/remote/1460000010988015" alt="16uoPajbFeKcVXdwDSuGxb7unYy1X1rMss" title="16uoPajbFeKcVXdwDSuGxb7unYy1X1rMss"></p>
[译文]2017年投资比特币和比特币现金,该考虑什么?
https://segmentfault.com/a/1190000010891727
2017-08-28T18:11:44+08:00
2017-08-28T18:11:44+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>原文链接:<a href="https://link.segmentfault.com/?enc=I6%2FI58FN4ASD6b7mJ3pCEg%3D%3D.Q4vA9N1tPy%2FZMh7qWZ6fWBZphU21wfcQO4idNfufnhrR%2BHyIjGxNDd%2BbCJ8jEPY%2BfasERJiW3LY0%2FuZW%2B3ZLHMzPbRGsLYYnGk6b%2F4b0FewTGkMhfVwHeHuoZIl4mmS4%2BhqogGvS2M751ZmkM3iD%2FwvP5adbdzsi8BPmmliWnD4%3D" rel="nofollow">https://medium.com/@jonaldfyookball/what-to-consider-when-investing-in-bitcoin-and-bitcoin-cash-in-2017-e6a47966a5c2</a></p>
<p><em>免责声明:本文不作为投资建议,只是我(作者)个人的观点,加密数字货币是一个充满风险的行业,永远不要进行超过你自身承受能力的投资,在做任何投资之前最好咨询下专业的建议。</em></p>
<p>投资比特币或者比特币现金既有巨大的风险,同时也带来了巨大的机遇。</p>
<p>在这篇文章中,我将解释什么让比特币产生了价值,并描述比特币以及比特币扩容相关的历史问题。我们将重点分析比特币和比特币现金,以及将要来临的SegWit2x,并分析他们带来的影响和动态。</p>
<h2>这是一个令投资者困惑的时代</h2>
<p>一些快速的技术分析:</p>
<p>自今年1月以来,比特币的价格已经上涨了3倍多,出现了抛物线式的增长曲线。对于投资者和交易员来说,这是一件比较棘手的事。</p>
<p>走势比任何人预计的都要长得多,这让市场变得风险很大。与此同时,这样的市场往往会出现很大的调整,这些都会让交易者去追逐市场。</p>
<p>如果认为比特币不是一个历史性的机遇,最好的办法就是等待市场变的更加清晰和变的更加大。</p>
<p>现在让我们看下基本面,你可能想知道:<br>"为什么比特币的价值会激增?"<br>"比特币还会继续升值吗?"<br>"比特币现金又是什么?"<br>"我一直听到的'SegWit'和'SegWit-2x'又是什么?"</p>
<h2>最初的比特币</h2>
<p>比特币有许多特性,这使它成为可以想象到的最理想的货币形式之一。</p>
<p>总的来说,比特币比法币(政府发行的)更稀缺、更耐用、可携带、可分割、可交换、它就像黄金一样的贵金属。比特币与其他形式的货币相比,验证其真实性更容易,更难被伪造。</p>
<p>比特币是一种支付系统,它比任何传统支付系统都更快、更便宜、更可靠。你可以在世界上任何地点,任何时间发送比特币,而且几乎是免费的。</p>
<p>比特币最大的弱点在于它并没有像其他形式的货币一样被广泛的使用。因此,它有可能永远无法获得主流市场的采纳,然而,与此同时,它也有其独特的潜力。这就造成了高风险,高回报的场景,也是早期的采用者并没有成功的原因。</p>
<p>在这个阶段,仍有大量的机会可以投资,可以说这是投资的最佳时机,因为仍有巨大的上行空间,而且风险要比5年前小得多。加密货币显然会一直存在下去。正因为如此,主流的对冲基金和投资者开始了大举投资比特币。</p>
<h2>比特币扩容的争论</h2>
<p>不幸的是,如果比特币不能满足越来越多用户的需要,比特币(以及其基本价值)的承诺就受到了威胁。随着比特币变得越来越缓慢,越来越昂贵,这种威胁已经存在了。</p>
<p>在过去的四年里,比特币世界里面一直处于战争的状态,比特币一直与如何扩充“网络规模”做斗争。换句话说:<strong>如果增加比特币交易容量来容纳更多的用户</strong>。</p>
<p>虽然这些对于那些了解的人来说可能是一个旧消息了,但如果你是投资者的话,这一点非常重要。</p>
<p>有两个相互竞争的观点,其中一派的观点与比特币的创造者Satoshi Nakamoto的著作和他对于比特币的愿景是一致的,即“peer to peer electronic cash”。这一派主张通过增加比特币的"块"大小来扩大网络规模。</p>
<p>第二种思想,主要来自于被称谓“Core”(比特币核心开发者)的开发者所拥护,即区块链无法有效地进行规模扩张。他们认为,既然比特币不能扩大规模,它就应该成为一个在顶层运行的二级系统的结算层。</p>
<h2>比特币核心开发者的误导</h2>
<p>不是我不礼貌,但是有时候事实会让一些人不愉快的。</p>
<p>尽管这听起来很让人惊讶,但是一个事实是,比特币核心开发者(Core)已经多年来一直在传播错误的信息,并且进行了大规模的<a href="https://link.segmentfault.com/?enc=zNSJt0TDmRZwedunQ4crGA%3D%3D.H%2FeFudUVIAIJa21IzCArIUFkt4dtVKMyvlO2vEpaca%2FeVxiiXTgyPpEwMLOn%2FhAiJT5rZT0qnBAPRMu8lqlMJBeYNiRkqaQkupnESn4xlpJpW44lF0xovbCmVs8aRp8N56HPy%2BC9kLy5xnCireJdUQ%3D%3D" rel="nofollow">审查活动</a>,以阻止对于这些问题(译者注:指扩容)的公开和诚实的讨论。</p>
<p>这可能是有争议的,但它们是高度相关的,我可以给你一些可以使用的信息--不撇开重要的话题。如果你们不相信我,可以阅读我之前的一些<a href="https://link.segmentfault.com/?enc=TM74ir9Bmv9axpEPLu1RUw%3D%3D.r%2BfjUXEcv3KaSy0eg3iKECSTq%2FpsBVKZFLF%2FoIxQQMwhbftZZHANP%2Bv7cbLZ6SFr" rel="nofollow">文章</a>,里面会更多的详细来讨论这个问题,你也可以自己研究一下。</p>
<p>无论如何,“Core观点”的主要主题之一,就是硬分叉是危险的,硬分叉是一个不能向后兼容旧网络节点的系统升级。</p>
<h2>SegWit</h2>
<p>作为这一神话的一部分,必须避免使用硬分叉,比特币核心开发小组(Core)在2015年提出了一个解决方案,称为"隔离见证"(SegWit),SegWit是复杂的(5000+行代码), 并引入<a href="https://link.segmentfault.com/?enc=XMc6to1zgJUQptitt0ck9Q%3D%3D.1XaocJTFYBARaDhCh86crGwQqWJC8ili9S7eeY4DiMjYOydfWDzPJigg8g6PhOn%2BBlsQ%2FQCW%2BCkkUgxwJ%2FvQW1s3RDU9Y67VzngoRaeeI0LM4ynRUCDld48ZaVKPAGwR" rel="nofollow">激进的变化</a>和<a href="https://link.segmentfault.com/?enc=uARrued2WFsiX2eCVyY6eA%3D%3D.KN30h9V5GnwVgyFc1Ap6Tb5Nyy%2BY5xPT%2BirMPwLwB6z8gjdNk98Eyflt5AzidqjDfIQrKKa1pV9AJQedB3ruRw%3D%3D" rel="nofollow">危险的经济奖励</a></p>
<p>同时,那些只是想要更大块的人们提出了一种解决方案,增加了一种名为Bitcoin Unlimited("BU")的软件版本。</p>
<p>在2017年初,BU占据了大约40%的矿工支持,而SewWit只有30%左右的支持,任何一方都不能达到强制升级系统所需要的大多数。</p>
<h2>纽约共识,UASF和UAHF</h2>
<p>在争议长期持续的僵局之下,在2017年5月达成了一项名为"SegWit 2x"的协议,它的意图是妥协,包括首先激活SegWit,然后在6个月内将块大小从1M扩展到2M(译者注:494784执行硬分叉)。</p>
<p>该协议是在纽约市的闭门会议上制定的,该会议包含了大量的业内企业,并不包含“Core”。</p>
<h4>事情从这里开始变得更加有趣了:</h4>
<p>“2x”的事情正在进行中,一个(据称是)基层强制激活SegWit的方案得到了很大的关注。它被称为用户激活软分叉(UASF).</p>
<p>为了应对具有破坏性的UASF,比特大陆制定了一个用户激活硬分叉(UAHF)的防护措施。</p>
<p>如果SegWit2x协议没有执行,UAHF仅仅只是一个应急措施,然而,2x协议确实向前推进了,比特大陆并没有实施UAHF计划。</p>
<p>但是出乎所有人的意外,UAHF仍然被激活了(即使2x协议仍然已经在执行了),而且实现了一个新的软件叫做“Bitcoin ABC”.</p>
<p>这种尝试有很多的牵引力,并导致了比特币的一个硬分叉,成为一种称为比特币现金的新货币,它将块大小增加到8M,并且不使用SegWit。</p>
<h2>Bitcoin Core , Bitcoin Cash 和 Bitcoin 2x</h2>
<p>截止到2017年8月1日,Bitcoin Cash 以自己的货币存在:比特币的分叉,意味着截止到8月1日的所有Bitcoin的持有人也成为Bitcoin Cash的持有人。</p>
<p>Bitcoin Cash 目前是第四大加密货币,市值为50亿美元,价格为300美元(译者注:当前是第三个加密货币,市值是100亿美元,价格是600美元)。</p>
<p>93%的矿工表明了自己支持纽约共识,但是"Core"从来都不是这个协议的一部分,实际上他们修改了他们的软件,用来拒绝支持SegWit2x的软件。</p>
<p>这可能意味着,在11月份,比特币可能会再次分裂,我们可能会拥有3种不同版本的比特币。</p>
<p>有很多理由表述为什么会发生或者不会发生,但我们不讨论这些。相反,我们来看看可能的结果。</p>
<h2>有什么意义?</h2>
<p>如果SegWit2x没有激活,那么只有Bitcoin Core和Bitcoin Cash。可以说,这对Bitcoin Cash 是有好处的,因为两种比特币之间会有更多的区别,Bitcoin Cash具有明显的可扩展性优势和较低的费用。</p>
<p>如果SegWit2x确实激活(且没有分裂),对于Bitcoin Cash 来说,至少在短期内可以说是不利的,而且对于Bitcoin来说是利好,因为它将具有很强的硬分叉和增加块大小的能力。</p>
<p>第三种情况(比特币分裂,我们将有3种比特币)更加的不可预测。这可能对现有的比特币持币用户有好处,因为他们将会拥有两条链上的资产,就像Bitcoin Cash分裂的时候发生的一样。</p>
<p>所有3个版本是否都能够存活是值得怀疑的。可能最不可能生存的版本是纯SegWit版本,因为它有的SegWit2x都有,它没有的SegWit2x也有。而且SegWit版本只有更少的算力支持。</p>
<p>然而,Core团队可以将其分配到不同的工作量证明算法中,有可能会产生一些惊喜。</p>
<h2>硬分叉恐惧</h2>
<p>市场似乎并没有被Bitcoin Cash的分裂而吓到,在8月1日之前,价格还上涨到了一个高潮,因为每个人都想收到他们的“免费币”,也许会出于同样的原因,市场不会害怕SegWit2x的分裂。</p>
<p>有可能太多的分裂会使投资者对比特币品牌失去信心,因为它将被视为不稳定的。但是我不认可这种看法,因为在很多人都知道的并且可以解释的历史上,将对他们的投资提供明确的保护。</p>
<p>对于我来说,如果Bitcoin也增加了2x,Bitcoin显然比Bitcoin Cash更具有竞争力。但这样做明显不符合Core的议程。</p>
<p>比较奇怪的是,很多人认为Bitcoin不会升级到2x,因为许多"小区块者"(Core支持)和“大区块者”(Bitcoin Cash支持者)都不欢迎2x的升级。</p>
<h2>比特币最大和网络效应</h2>
<p>Bitcoin比任何其他加密货币具有更大的市值,主要原因是它具有先发优势,拥有最大的网络。</p>
<p>所谓的网络效应是自我维持的,因为用户(新的和已存在的)自然会被吸引到使用最大的网络,因为它拥有最大的收益。这也是Facebook没有激烈竞争者的原因。</p>
<p>然而,网络效应不是绝对的。如果网络变得不可靠、不再好用,它将失去它的用户。而如果另一个网络出现明显的好转,那么它将开始从旧的,较大的网络中吸引用户的情况,直到新的网络成为王者。</p>
<p>这正是当Facebook将MySpace替代成为主要的社交媒体网络的原因,Facebook的用户体验要好很多。</p>
<p>尽管Bitcoin Cash 采用了不同的方法,但Bitcoin Cash是Bitcoin的直接竞争对手,也许只有一种比特币会占据用户、商家、投资者和矿工。</p>
<p>那时它就是‘比特币’。</p>
<p>但请记住,网络效应可能会收到侵蚀和挑战。如果Bitcoin不能很快提供有竞争的手续费,它将继续失去用户和商家到Bitcoin Cash 。投资者将跟随,然后是矿工。</p>
<h2>机构投资者不一定是比特币中最聪明的钱</h2>
<p>通常,机构投资者是专业人士,被认为是“最聪明的钱”,因为他们以投资和交易为生。</p>
<p>但是,Bitcoin是一种新的资产分类,很多对冲基金甚至不足以参与到游戏中。许多没有投资Bitcoin的专业投资者都表示<a href="https://link.segmentfault.com/?enc=ooL5jcJLrvkuGjYhvB7R0w%3D%3D.dD6sZkl%2FMgBCYnmNiS6wIgzTlV8koJ7yZzBcAdXd5aCWOsTuZI2OuCda7aUVeRjVGyHdssJ%2FHS3aHNMS3Bo%2Bpw%3D%3D" rel="nofollow">"我对此不了解"</a>。</p>
<h2>比特币是泡沫吗?</h2>
<p>在没有真实(非投机性)需求时,市场将会泡沫流行甚至崩溃。在这种情况下:当货币实际上并不被用作支付的时候,它是建立在其声誉上的。</p>
<p>一些主流的货币已经使用了SegWit并称其是一个好的扩容解决方案,<a href="https://link.segmentfault.com/?enc=RXhrGsvTZjtbDctOpeKn%2Bw%3D%3D.jDesIioa8EGPZZOhB3EqC5VgpTrmqm%2FovFr%2F4XL0eE2p1kNSPEqQTWH%2FxYxOoc225eS0ipT9QlJo%2Fm0k2fDG2w%3D%3D" rel="nofollow">但是现实中并不是这样</a>。此外,许多人错误地假设SegWit2x是一项已完成的交易。</p>
<p>到目前为止,我还没有把Bitcoin的价格行为看作是“泡沫”,因为从根本上来说,Bitocin是一种优越的支付系统和货币形式。</p>
<p>然而,如果投资继续流入Bitcoin的中,实际上它并不是一个有用的支付系统(因为Bitcoin价格昂贵且缓慢),那么真正的泡沫将会形成,对投资者来说可能是一个可怕的结果。</p>
<h2>一个成功的投资策略</h2>
<p>Bitcoin Core的核心路线图是构建第二层网络,但是这些层面在技术和需求方面都是未经证实的,并且可能还不如传统的on-chain的比特币。</p>
<p>这就是为什么有些人说Bitocin Cash是真的比特币,因为它将继续运行比特币一直运行的方式,而Bitcoin Core则希望进行彻底的改变。</p>
<p>Bitcoin Cash有更好的基本面,但是网络较小。在某种程度上,Bitcoin Cash vs Bitcoin相比,就像Bitcoin第一次出现的时候拿Bitcoin 和 Fiat(译者注:法定货币(英语:Fiat Money),简称法币,是政府发行的纸币。发行者亦没有将货币兑现为实物的义务,只依靠政府的法令使其成为合法通货的货币。法定货币的价值来自拥有者相信货币将来能维持其购买力,但货币本身并无内在价值Intrinsic value)对比一样。</p>
<p>不同的是,Bitcoin可以通过大区块,吞并Bitcoin Cash的价值主张。但是,这似乎是不太可能发生的,因为Bitcoin Cash出现的全部原因就是因为要阻止这种情况(译者注:大区块)在Bitcoin上的发生。</p>
<p>持有Bitcoin 和Bitcoin Cash是一种成功的策略,至少有一个在继续实现“peer to peer electronic cash“,从而将比特币在未来真正的应用起来。</p>
<p>以这种方式,Bitcoin Cash就像一个持续的监管机构,无论何时,Bitcoin Cash都会一直存在那里。</p>
<h2>挖矿战争</h2>
<p>让这一切更加复杂的事情是,Bitcoin Cash 使用的是和Bitcoin相同的SHA-256验证算法,这意味着投资在ASIC芯片上的矿工可以挖任意的一个链。</p>
<p>如果我们假设矿工是理性的且是利润驱动的,他们将会在最有利可图的链上进行挖矿。</p>
<p>Bitcoin Cash 开始和Bitcoin拥有相同的挖矿难度,当它分叉之后,使用了一个新的算法将难度快速下调。</p>
<p>如果Bitcoin Cash比Bitcoin更长时间的可以赚取利润或者更加的有利可图,那么会导致大量的矿工转到Bitcoin Cash的链上。</p>
<h2>抢夺?</h2>
<p>如果矿工从Bitcoin上离开,转移到Bitcoin Cash ,它可能会产生毁灭性的后果,因为Bitcoin只能在2016个块进行难度调整,对于Bitcoin来说,最糟糕的情况是,它被卡住了,被迫使用一个新的POW算法。</p>
<p>如果这种情况发生,Bitcoin Cash 将在很短时间内爆炸式增长,而比特币的价格将会暴跌。</p>
<p>这可能是Bitcoin现在价格如此之高的原因之一。也许正在抽水,以防止矿业利润持平。</p>
<p>鉴于所有这些因素,对Bitcoin Cash 的投资在当前时间具有有利的回报率(在我看来)。</p>
<p>但是我们也需要承认,Bitcoin已经呈现了令人印象深刻的韧性和全球性需求,所以在考虑投资的时候要谨慎行事。</p>
<h2>结论</h2>
<p>这是比特币的一个复杂时代。我鼓励你们自己去做自己的研究和思考。就我个人而言,我持有比特币,同时继续积累比特币现金。</p>
<p>如果我今天是一个全新的投资者,我会考虑用美元成本的平均策略购买少量的这两种资产。</p>
比特币:交易的数据结构
https://segmentfault.com/a/1190000010224073
2017-07-17T18:11:09+08:00
2017-07-17T18:11:09+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>比特币协议中最重要的部分就是交易,比特币协议其他的部分也都是为了确保交易的生成、广播、验证和打包而实现的。<br>本文内容主要是针对交易的数据结构以及对原始交易进行解析,后期还会继续写<code>交易的生命周期</code>、<code>交易脚本</code>等文章。</p>
<h2>原始交易</h2>
<p>比特币的交易是以字节的形式存在块中的,使用<code>bitcoin-cli</code>命令可以获得一个原始的交易数据。例如:</p>
<blockquote><p>bitcoin-cli getrawtransaction 2eb0e06af852f049f7dc641f740ded17a11cde138fd3d3d3c4a078649c053260</p></blockquote>
<p>会得到一个完整的原始交易数据:</p>
<pre><code>010000000112255d3c...88ac00000000[^rawtransaction]</code></pre>
<p>得到的原始交易是经过<code>hex</code>处理的,并没有显示为原始字节。从上面的<code>hex</code>字符串也并不能看到有用的信息(如果想查看json格式的交易可以查看<a href="https://link.segmentfault.com/?enc=DNT2RoQMWfoxmCuQRhYe7A%3D%3D.Mn6gAG8E0AOOOc14I7ONqyE6hs1IbFOb%2FVkh8HJvDr%2BJbq%2BfW1bBbGkgg9JCwTM8KCIvHFHix5xEmL20OlJHYx%2FQXEMuWbIKgEG0h1H9aiM0h56L8oFqDtW9grkIw1KbAcKmonklM33QeAiwFJVtwQ%3D%3D" rel="nofollow">api</a>).<br>原始的交易数据并不能直观地体现交易的具体内容,如何才能得到具体的交易内容呢?借助这个例子可以详细的分析下交易结构和原始数据解析。先看下交易的数据结构。</p>
<h2>交易的数据结构</h2>
<p>一个完整的交易由以下的元素构成的:</p>
<ul>
<li><p>版本 version</p></li>
<li><p>输入 tx_in</p></li>
<li><p>输出 tx_out</p></li>
<li><p>锁定时间 lock_time</p></li>
</ul>
<p>其中交易的输入和输出有可能是一个或多个,上面所说的交易就有一个输入和两个输出。<br>版本(version)是明确一笔交易参照的规则,除非有重大升级的情况下,版本号基本无变化,是比较固定的一个值。<br>交易的锁定时间是被该交易被加到区块的最早时间,在大多数的情况下他的值都是0,表示需要立即被加入区块中。如果锁定时间大于0而小于5亿,它的值就表示区块高度。如果大于5亿就表示一个Unix时间戳。<br>上面所描述的交易构成又是如何在原始交易里面体现的呢?这就需要对原始交易进行拆解,看下交易结构如何在原始交易中定义的:</p>
<table>
<thead><tr>
<th>描述</th>
<th>长度</th>
<th>原始字段</th>
</tr></thead>
<tbody>
<tr>
<td>版本</td>
<td>4</td>
<td>01000000</td>
</tr>
<tr>
<td>交易输入的个数</td>
<td>1+</td>
<td>01</td>
</tr>
<tr>
<td>交易输入</td>
<td>41+</td>
<td>12255......ffffffff <sup><a class="footnote-ref">1</a></sup>
</td>
</tr>
<tr>
<td>输出的个数</td>
<td>1+</td>
<td>02</td>
</tr>
<tr>
<td>交易输出</td>
<td>9+</td>
<td>00e1f...988ac<sup><a class="footnote-ref">2</a></sup>
</td>
</tr>
<tr>
<td>锁定时间</td>
<td>4</td>
<td>00000000</td>
</tr>
</tbody>
</table>
<p>通过上面的表格可以看到一个原始交易是如何存储交易的。原始交易中除了包括交易所需要的数据之外,因为交易的输入和输出有可能会出现多个,所以原始交易还需要额外的字段用来描述交易的输入、输出个数,输入、输出的个数并不是固定的,因此用来描述个数使用的是变长整数(VarInt),他的目的是在节省空间的情况下能够存储足够使用的整数。<br>这里描述了一个交易的数据结构以及他在原始交易中如何存储的,但是并没有拆解出具体的<code>输入</code>、<code>输出</code>内容。下面就对<code>输入</code>、<code>输出</code>进行详细的拆解,先从输入开始。</p>
<h2>交易输入</h2>
<p>一个交易的输入是由下面的元素组成:</p>
<ul>
<li><p>引用交易的hash</p></li>
<li><p>引用交易的索引</p></li>
<li><p>解锁脚本</p></li>
</ul>
<p>那么交易输入又是如何在原始交易里面进行存储的呢?从上面的分析我们可以知道该例子交易中只有一个交易的输入。下面就是从原始交易中解析下交易的<code>输入</code>:</p>
<table>
<thead><tr>
<th>描述</th>
<th>长度</th>
<th>原始字段</th>
</tr></thead>
<tbody>
<tr>
<td>前置交易hash</td>
<td>32</td>
<td>12255...ae<sup><a class="footnote-ref">3</a></sup>
</td>
</tr>
<tr>
<td>前置交易的索引</td>
<td>4</td>
<td>01000000</td>
</tr>
<tr>
<td>解锁脚本长度</td>
<td>1+</td>
<td>8a</td>
</tr>
<tr>
<td>解锁脚本</td>
<td>?</td>
<td>4730...eae<sup><a class="footnote-ref">4</a></sup>
</td>
</tr>
<tr>
<td>序列</td>
<td>4</td>
<td>ffffffff</td>
</tr>
</tbody>
</table>
<p>这里获取到的hash和网站上面显示的hash并一致,这是因为字节序的存储和读取的方式不一致造成的,即<a href="https://link.segmentfault.com/?enc=TRyLEq8WEUDFMxTzb5pksg%3D%3D.qZJcvjAcTYlCv5QcAhMXpxfozRclsh64uzZ6hT0f2z4HPmIihle9cH4CIFqPVp87QLtQUR9o3Cuq4YkGKGzElA%3D%3D" rel="nofollow">大端和小端模式</a>:</p>
<blockquote><p>网络协议规定接收到第一个字节是高字节,存放在低地址,所以发送时会首先去低地址取数据的高字节</p></blockquote>
<p>而在比特币的存储中hash是做为一个整数存储的,因此在取hash时候需要从低地址开始获取。<br>而解锁脚本的长度也是未知的,就需要使用一个可变整形用来表示解锁脚本的长度。对于<code>交易脚本</code>的拆解会在以后的文章中进行。<br>通过上面表格的描述就可以从一个交易中拆解出它的输入了,下面继续对交易输出进行拆解。</p>
<h2>交易输出</h2>
<p>一个交易的输出是由下面的元素组成的:</p>
<ul>
<li><p>输出金额</p></li>
<li><p>输出脚本</p></li>
</ul>
<p>那交易的输出在原始交易中又是如何存储的呢?从上面的交易拆解中可以知道该例子交易是有两个输出,这里只需要针对第一个输出进行拆解即可:</p>
<table>
<thead><tr>
<th>描述</th>
<th>长度</th>
<th>原始字段</th>
</tr></thead>
<tbody>
<tr>
<td>value 单位是1聪</td>
<td>8</td>
<td>00e1f50500000000</td>
</tr>
<tr>
<td>锁定脚本长度</td>
<td>1+</td>
<td>19</td>
</tr>
<tr>
<td>锁定脚本</td>
<td>?</td>
<td>76a9147072795a259b38bf476e053852ab85221ba9467b88ac</td>
</tr>
</tbody>
</table>
<p>注意输出的金额也涉及到大端传输的问题,解析的时候需要从低地址开始读取。<br>这里并没有对<code>锁定脚本</code>进行拆解,所以还看不到输出的地址,对于一个比特币交易来说,交易本身是不用关心输出的地址,交易只需要关心锁定脚本,当使用的时候能使用使用正确的解锁脚本即可动用比特币。关于<code>交易脚本</code>会在以后的内容里详细的介绍。<br>交易本身的数据结构并不没有<code>交易费</code>的概念,每笔交易的手续费是使用<code>总输入-总输出</code>计算得到的,所以在交易的数据结构中没有体现。<br>总结,一个原始的交易包含了一个交易所需要的所有数据,他们按照比特币协议规定的规则进行存储。在交易生成,验证的时候也需要按照xiang'tong</p>
<hr>
<ol>
<li> 12255d3cd1e5a59bec64057b0d2b2a7f3c9a9e1f14d0f1b362b72e96743d69ae010000008a473044022065d352a27ed3039e7fbca5315c38b5d255e68e9919964906c5dfe3cfea7abe11022070036614521710506873b769ff8bb53dc7350f752fc687ed483713eca136b611014104d5d461083771ac542a6417a8424b74ba56d47f77e888cde408a508189d88bcef9bbb7292b750774da227dbd326db2a2efbeaab9789e57b946a41ab895c0d2eaeffffffff <a class="footnote-backref">↩</a>
</li>
<li> 00e1f505000000001976a9147072795a259b38bf476e053852ab85221ba9467b88acc0570100000000001976a9140cb6c275be7f179883bb821ef1dfd6b520fc656988ac <a class="footnote-backref">↩</a>
</li>
<li> 12255d3cd1e5a59bec64057b0d2b2a7f3c9a9e1f14d0f1b362b72e96743d69ae <a class="footnote-backref">↩</a>
</li>
<li> 473044022065d352a27ed3039e7fbca5315c38b5d255e68e9919964906c5dfe3cfea7abe11022070036614521710506873b769ff8bb53dc7350f752fc687ed483713eca136b611014104d5d461083771ac542a6417a8424b74ba56d47f77e888cde408a508189d88bcef9bbb7292b750774da227dbd326db2a2efbeaab9789e57b946a41ab895c0d2eae <a class="footnote-backref">↩</a>
</li>
</ol>
SegWit2x客户端安装教程
https://segmentfault.com/a/1190000009980195
2017-06-29T14:46:25+08:00
2017-06-29T14:46:25+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>按照纽约共识的路线图现在SegWit2x已经进入了测试阶段,SewWit2x的开发工作由Jeff Garzik亲自操刀,Jeff也在呼吁大家对SegWit2x进行测试,SegWit2x的项目地址为:<a href="https://link.segmentfault.com/?enc=flqg4g%2B%2BmXLPldVsmBibxg%3D%3D.76a73vudC3oUPgQa0bAYN%2FiaOirH0J6XrL%2FfPSv2r3o%3D" rel="nofollow">https://github.com/btc1/bitcoin</a>。<br>本文的目的是让读者可以尽快的安装SegWit2x版本的客户端,并可以对其进行测试(运行testnet5)。本文使用Ubuntu操作系统为例。</p>
<h2>源代码下载</h2>
<p>目前SegWit2x还没有发布可执行文件,只能自己编译源代码进行安装。源代码也有两个版本:releases 和 git版本库。 <br>releases 版本可以直接下载,地址:<a href="https://link.segmentfault.com/?enc=9fpCl10QgsQYvoPxr%2F9dBw%3D%3D.9WIpMdvrYU%2B0tQOmcyE7EKsbjUVOvRgZ7qYz179FEt6nSWVjGaRyL1qxj2fWvqad" rel="nofollow">https://github.com/btc1/bitcoin/releases</a>,可以看SeWit2x的最新的releases以及历史releases版本。<br>在ubuntu上可以使用<code>wget</code>命令下载releases的代码,下载<code>1.14.1rc2</code>版本的命令为:</p>
<blockquote><p>wget <a href="https://link.segmentfault.com/?enc=X6bFUtWv1UES37V8D53QCw%3D%3D.tyf7a1xZCzhXp9ul%2Bg%2B0uCU774rLRk415Mn6Vnzpk9Z%2FehxBd1WKwopAm88xijry8CmTy5ylphT4jsLOyuBWzw%3D%3D" rel="nofollow">https://codeload.github.com/b...</a></p></blockquote>
<p>下载之后解压命令:</p>
<blockquote><p>tar -xzvf v1.14.1rc2</p></blockquote>
<p>如果不想使用releases版本可以在git版本库中直接clone开发中的代码。克隆命令为:</p>
<blockquote><p>git clone git@github.com:btc1/bitcoin.git</p></blockquote>
<p>注意SegWit2x的代码提交在<code>segwit2x</code>上,不在<em>master</em>分支上,切换git分支的命令为:</p>
<blockquote><p>git checkout segwit2x</p></blockquote>
<h2>依赖库安装</h2>
<p>编译源代码需要先安装对应的依赖库,在Ubuntu可以直接使用命令行安装依赖库,本文只以安装bitcoind为例,不包含bitcoin-qt和wallet的安装。<br>安装依赖库:</p>
<blockquote><p>sudo apt-get install build-essential libtool autotools-dev automake pkg-config libssl-dev libevent-dev bsdmainutils</p></blockquote>
<p>安装boost的依赖:</p>
<blockquote><p>sudo apt-get install libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-program-options-dev libboost-test-dev libboost-thread-dev</p></blockquote>
<p>安装boost的开发包:</p>
<blockquote><p>sudo apt-get install libboost-all-dev</p></blockquote>
<h2>编译源代码</h2>
<p>进入SegWit2x的目录,运行<code>autogen.sh</code>命令:</p>
<blockquote><p>./autogen.sh</p></blockquote>
<p>上面的命令完成之后运行<code>configure</code>命令且指定不包含图形界面和钱包:</p>
<blockquote><p>./configure --without-gui --disable-wallet</p></blockquote>
<p>直接运行编译命令:</p>
<blockquote><p>make && make check</p></blockquote>
<p>安装SegWit2x版的bitcoind到系统中:</p>
<blockquote><p>sudo make install</p></blockquote>
<p>现在系统中就有了SewWit2x的bitcoind,下面就可以直接运行了。</p>
<h2>运行testnet5</h2>
<p>SegWit2x运行在测试网络<code>testnet5</code>上面,SegWit2x客户端已经更改<code>-testnet</code>为<em>testnet5</em>,SegWit2x客户端安装成功后直接运行测试网络的命令即可运行在testnet5上,运行测试网络命令:</p>
<blockquote><p>bitcoind -testnet --daemon</p></blockquote>
<p>可以通过bitcoin-cli命令查看当前节点的运行情况,需要加上<code>-testnet</code>:</p>
<blockquote><p>bitcoin-cli -testnet getinfo</p></blockquote>
<p>另外,SegWit2x的数据放在目录<code>~/.bitcoin/testnet5</code>中。SegWit2x相关的数据浏览可以在<a href="https://link.segmentfault.com/?enc=zTL775cKbRo%2BokTvYeU%2BOQ%3D%3D.lPVg0fZjHaFzDsMBFogf1xgzeE7iApRIeIEihBtxmhSVJ%2FPrbKrq6Wfls5BJi6Ue" rel="nofollow">https://testnet5.blockchain.info/</a>上查询。<br>文中使用的脚本可以在<a href="https://link.segmentfault.com/?enc=xCrPzw5N4eu1aSMs8Xu%2FYQ%3D%3D.RkeESH3IMV%2F0XhjwX9C7kEOnyHzjl9ns3cpomJyOHxcbgJ5KwnlU3%2BK%2BhYwIqHsL7kmXNQtmkguOOaMZgTKxZw%3D%3D" rel="nofollow">https://github.com/jjz/script/blob/master/segwit2x_install.sh</a>中获取。</p>
Agora iOS SDK-多人聊天
https://segmentfault.com/a/1190000009357572
2017-05-10T09:44:41+08:00
2017-05-10T09:44:41+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在上一篇<a href="https://link.segmentfault.com/?enc=ir5o%2FF2%2FK7iHgxP90J7EkQ%3D%3D.nk8xpEC%2FVJmLzAx7sST22GI5gyTWB3GrxMXVDpy659yMXvnWRLqX%2FJqF9CAn4bRS" rel="nofollow">Agora iOS SDK-开始聊天</a>介绍了如何使用Agora SDK进行一对一的聊天,这篇主要介绍下如何使用Agora iOS进行多人聊天,需要实现的功能:</p>
<ol>
<li><p>随着加入人数的变化,而显示不同的UI,主要是分屏</p></li>
<li><p>在多屏显示的情况下,点击一个小窗,会放大显示该聊天窗</p></li>
<li><p>前篇实现的聊天功能</p></li>
</ol>
<p>实现上面所说的功能:<code>分屏</code>,最好的方式是使用瀑布流布局,这样可以满足分屏的需要。</p>
<h2>瀑布流</h2>
<p>分屏显示最好的方式是采用瀑布流,这样比较方便的能够适应UI的变化。<br>瀑布流的实现方式比较多,常用的方式是使用<code>UICollectionView</code>实现,自定义一个<code>UICollectionViewLayout</code>。UICollectionViewLayout是向UICollectionView提供布局信息,也包括对于视图的布局信息。需要重载UICollectionViewLayout的以下方法:</p>
<ul>
<li><p>collectionViewContentSize 返回内容的大小</p></li>
<li><p>(UICollectionViewLayoutAttributes <em>)layoutAttributesForItemAtIndexPath:(NSIndexPath </em>)indexPath 根据位置反馈cell对应的布局属性</p></li>
<li><p>(NSArray<UICollectionViewLayoutAttributes <em>> </em>)layoutAttributesForElementsInRect:(CGRect)rect ,Cell的布局方式</p></li>
<li><p>prepareLayout 初始化方法</p></li>
</ul>
<p>具体的实现可以参考<a href="https://link.segmentfault.com/?enc=EbVv9eOHhVgF7fL%2B1uwjWQ%3D%3D.iy6kGuUyCD%2Fq4MEKOdeJwCevaVr84s0TxcNZ%2BLZhJjfDnXu4Z5Pvvp4SYuGp9BR4" rel="nofollow">https://github.com/LD1314/LDWaterflowLayout</a>,该demo中也会使用该实现。<br>有了一个已经实现的瀑布流之后,下面就可以实现分屏了。需要引入<code>LDWaterflowLayout</code>。</p>
<h2>动态聊天窗</h2>
<p>新建一个ViewController类,命名为<code>MutilChatViewController</code>,用来做分屏显示的需求,在Agora SDK中一个远程视频的显示只和该用户的uid有关,所以使用的数据源只需要简单定义为包含uid即可,定义为:</p>
<pre><code>public var dataArray:Array<UInt>?=Array<UInt>();</code></pre>
<p>在Agora的委托<code>AgoraRtcEngineDelegate</code>中当一个用户加入聊天之后会触发它的一个方法,实现该方法,把用户的uid加入dataArray中:</p>
<pre><code class="swift">func rtcEngine(_ engine: AgoraRtcEngineKit!, didJoinedOfUid uid: UInt, elapsed: Int) {
if(!(dataArray?.contains(uid))!){
dataArray?.append(uid)
collectionView.reloadData()
}
}</code></pre>
<p>对于瀑布流的实现需要MutilChatViewController继承:</p>
<ul>
<li><p>UIViewController</p></li>
<li><p>UICollectionViewDataSource</p></li>
<li><p>LDWaterflowLayoutDelegate</p></li>
</ul>
<p>其中<code>LDWaterflowLayoutDelegate</code>就是瀑布流的委托,重写它的两个方法就可以实现分屏:</p>
<ol>
<li><p>columnCount(in waterflowLayout: LDWaterflowLayout!) -> CGFloat <br>该方法用来指定一行显示几个元素,这里采用比较简单的方式当只有一个1个用户的时候就显示在一行,2个用户的时候就在一行显示2个元素...4个用户的时候就显示为2行...</p></li>
<li><p>waterflowLayout(_ waterflowLayout: LDWaterflowLayout!, heightForItemAt index: UInt, itemWidth: CGFloat) -> CGFloat <br>该方法用来设定每个元素的高度,也是采用比较简单的方式,只用1行的时候高度为300,有2行的时候高度为150,3行的时候高度为100。</p></li>
</ol>
<p>把分屏的布局写好之后,就可以在每一个<code>UICollectionViewCell</code>上播放聊天视频了。</p>
<h2>播放聊天视频</h2>
<p>新建一个类ChatCell它继承了UICollectionViewCell,在ChatCell中有两个组件:<code> videoView: UIView!</code>和<code>labelUser: UILabel!</code>,前者用来显示用户视频,后者用来显示用户信息,videoView布局在整个ChatCell上,随着ChatCell的变化而变化。<br>要在ChatCell中播放聊天视频,必须在ChatCell持有<code>AgoraRtcEngineKit</code>的变量,因此需要ChatCell声明AgoraRtcEngineKit的变量,并且在实例化ChatCell之后给该变量赋值:</p>
<pre><code>public var agora :AgoraRtcEngineKit!</code></pre>
<p>之前说过播放远程用户的只需要实例化一个AgoraRtcVideoCancas之后再把uid赋值给它就可以了,而播放本地视频也是类似的方法,还需要区分一个用户是自己还是远程用户,因此需要传入当前用户的uid,在ChatCell中定义方法用来播放视频:</p>
<pre><code> func setUid(uid:UInt,localUid:UInt){
labelUser.text=String(uid)
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid=uid
videoCanvas.view=videoView
videoCanvas.renderMode = .render_Fit
if(uid != localUid){
agora.setupRemoteVideo(videoCanvas)
}else{
agora.setupLocalVideo(videoCanvas)
}
}</code></pre>
<p>这样在多人聊天的时候就能使用分屏的方式播放用户聊天视频了,如果想放大某一个用户的视频该怎么办呢?</p>
<h2>放大显示</h2>
<p>当用户点击某一个UICollectionViewCell的时候,希望对应的视频能够放大显示。因为一个视频的播放只能显示在一个view上面,所以必须在点击一个UICollectionViewCell的时候把它的播放显示移除掉,在放大区域播放该聊天视频,为了预留足够空间显示放大的时候,还需要调整UICollextionViewCell的高度,给放大显示预留出足够的空间。<br>因此首先我们在<code>MutilChatViewController</code>中新增一个显示放大区域的view:</p>
<pre><code>@IBOutlet weak var remoteView: UIView!</code></pre>
<p>在放大视频的时候,不知道用户是需要放大自己的视频,还是放大远程用户的视频,因此首先要记录下用户自己的uid,在点击的时候拿到用户的uid,再判断是显示本地视频还是远程用户的视频,放大视频方法:</p>
<pre><code class="swift"> func setupVideo(uid:UInt){
if(self.remoteView.isHidden){
self.remoteView.isHidden=false
}
let videoCanvas=AgoraRtcVideoCanvas()
videoCanvas.uid=uid
videoCanvas.view=remoteView
videoCanvas.renderMode = .render_Fit
if(uid==localUid){
agoraKit.setupLocalVideo(videoCanvas)
}else{
agoraKit.setupRemoteVideo(videoCanvas)
}
}</code></pre>
<p>当用户触发点击事件的时候使用变量<code>isSelect</code>记录用户的点击行为,如果用户有点击行为的时候,在判断Cell高度的时候就返回和Cell宽度一样的值(这里只是在demo的情况做的考虑,如果实际使用中还要根据Cell的个数进行显示高度的考虑),判断高度方法:</p>
<pre><code>func waterflowLayout(_ waterflowLayout: LDWaterflowLayout!, heightForItemAt index: UInt, itemWidth: CGFloat) -> CGFloat {
let count:Int=(dataArray?.count)!
if(self.isSelect!){
return itemWidth
}
....
}</code></pre>
<p>这样就可以在用户点击某一个Cell的时候进行放大显示的处理,在<a href="https://link.segmentfault.com/?enc=cRKNfnlJWvumUbDUWqfP8A%3D%3D.b6eRTrTFFfz3wsThOgZTLMPAzk4LH1Dj%2BlsmDCaH9Od4GIMyJya55z2oKjolfZ50" rel="nofollow">上一篇文章</a>中,介绍了使用<code>reportAudioVolumeIndicationOfSpeakers </code>监听是谁在说话,如果真实的项目中,在每一个Cell中可以做一个小广播,在某一个用户说话的时候,可以通过小广播的变化进行标识。<br>这里使用Agora SDK做了一个简短的demo,后续的还会继续完善,利用Agora SDK模仿<code>Housparty</code>的功能实现比较简单,先要产品化还有很多的东西要做,在这里先做一个简单的总结吧!</p>
<h2>Agora SDK使用总结</h2>
<p>Agora提供了高质量的视频通信SDK,覆盖了主流的操作系统,集成效率也比较高,而且还支持多个模式的视频通话,包括聊天,会议,直播等功能。SDK中API设计基本能够满足大部分的开发需要,而且隐藏了底层开发,这样对于应用层的开发者来说十分友好。非常适合有视频聊天开发需求的开发者。在视频领域创业大爆发的今天,建议更多的想要从事该领域的开发者可以尝试下。<br>在使用Agora iOS SDK的过程中有两个建议希望厂商可以考虑下:</p>
<ol>
<li><p>支持 Cocoapods,不支持Cocoapods需要在集成的时候在依赖上面花费时间,而且以后升级也不是太容易。</p></li>
<li><p>希望可以提供基础UI的SDK,就像友盟的分享SDK一样,提供一套可用的带UI的SDK,当然还要允许用户定制,这样应用层的开发者集成效率会更高。</p></li>
</ol>
<p>参考文档:<a href="https://link.segmentfault.com/?enc=nLmsNxoqobmEG69OjSwp%2BQ%3D%3D.Fuc2wY9poP5cm0HDHk7YQAFheeGbw9Ow5XAAsxLceROjApRUOSggtWyL4GRGKwgD%2BxUVgmV7aNSncfcwvAC2UQ%3D%3D" rel="nofollow">https://docs.agora.io/cn/user_guide/API/ios_api.html</a><br>demo地址:<a href="https://link.segmentfault.com/?enc=n1dmnBOjX7P7sqNlXSvDlw%3D%3D.PK9Dhbb9P5zVx3CGOd8bzrOd5epQgFbufuFxBmOtZ3qfDQntmufEjfQkCKljvhcE" rel="nofollow">https://github.com/jjz/agora-swift</a></p>
Agora iOS SDK-开始聊天
https://segmentfault.com/a/1190000009320166
2017-05-07T10:42:25+08:00
2017-05-07T10:42:25+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在上一篇<a href="https://link.segmentfault.com/?enc=05fHZSmxDMsXFM6j%2FcAeWA%3D%3D.SW%2FaDBw6%2FmSHjqHXSDzMJxehQbxrMvOdufPIA8RSe77r0ind16ZIbhG95aola3fH" rel="nofollow">Agora iOS SDK-快速入门</a>中聊了如果配置Agora iOS SDK,这一篇将看下如何使用Agora如何进行聊天。<br>Agora封装了视频聊天的大多数常用功能,直接调用Agora API即可直接开始聊天。这篇文章的主要目标是结合文档完成一对一视频聊天的Demo。</p>
<h2>初始化</h2>
<p><code>AgoraRtcEngineKit</code>是Agora SDK的入口,通过它就可以完成聊天的基本设置。比如设置远程视频、本地视频的配置、声音控制、以及设置摄像头等。<br>首先,新建一个<code>ChatViewController</code>用来实现聊天功能,在<code>ChatViewController</code>中声明一个AgoraRtcEngineKit的变量:</p>
<pre><code class="swift">var agoraKit : AgoraRtcEngineKit!</code></pre>
<p>初始化该变量需要实现委托:<code>AgoraRtcEngineDelegate</code>,它是AgoraRtcEngineKit的回调,在出现错误、离开频道....等情况出现的时候可以在该回调中得到通知。<br>它的其中一个方法<code>- (void)rtcEngine:(AgoraRtcEngineKit *)engine firstRemoteVideoDecodedOfUid:(NSUInteger)uid size:(CGSize)size elapsed:(NSInteger)elapsed;</code>的意思是在第一个用户准备好视频通信的情况下会触发该方法,这个时候就可以配置该用户的显示界面了,<code>AgoraRtcEngineDelegate</code>实现:</p>
<pre><code>extension ChatViewController:AgoraRtcEngineDelegate{
func rtcEngine(_ engine: AgoraRtcEngineKit!, firstRemoteVideoDecodedOfUid uid: UInt, size: CGSize, elapsed: Int) {
}
func rtcEngine(_ engine: AgoraRtcEngineKit!, didLeaveChannelWith stats: AgoraRtcStats!) {
}
}
</code></pre>
<p>这里使用了<code>extension</code>扩展了ChatViewController用来实现<code>AgoraRtcEngineDelegate</code>。<br>再实现了委托之后就可以实例化agoraKit了,实例化方法:</p>
<blockquote><p>agoraKit=AgoraRtcEngineKit.sharedEngine(withAppId: AgoraSetting.AgoraAppId, delegate: self)</p></blockquote>
<p>这样就完成了AgoraRtcEngineKit的初始化,初始化完成之后还需要设置视频显示,下面就先从远程视频的设置开始。</p>
<h2>开启远程用户视频</h2>
<p>远程视频的设置也比较简单,在<code>AgoraRtcEngineDelegate</code>中的方法<code>func rtcEngine(_ engine: AgoraRtcEngineKit!, firstRemoteVideoDecodedOfUid uid: UInt, size: CGSize, elapsed: Int)</code>被调用的时候,就可以开启该远程视频的显示,实现该方式就可以拿到该用户的信息。<br>在设置远程用户视频之前,还需要新建一个UIView用来显示远程视频,远程视频的显示会在该UIView内完成,新建一个UIView用来接收远程视频:</p>
<blockquote><p>@IBOutlet weak var remoteView: UIView!</p></blockquote>
<p>还需要一个VideoCanvas的实例,在该实例中配置远程视频的显示方式。</p>
<blockquote><p>let videoCanvas = AgoraRtcVideoCanvas()</p></blockquote>
<p>videoCanvas需要设置下下面几个参数:</p>
<ul>
<li><p>uid 用来区分用户的唯一标识</p></li>
<li><p>view 用来设置显示远程视频的view</p></li>
<li><p>renderMode 视频显示模式包括三种模式:AgoraRtc_Render_Hidden、AgoraRtc_Render_Fit、AgoraRtc_Render_Adaptive</p></li>
</ul>
<p>配置好videoCanvas之后,就可以在agoraKit中开启远程视频了:</p>
<blockquote><p>agoraKit.setupRemoteVideo(videoCanvas)</p></blockquote>
<p>使用Agora设置远程视频的播放就是如此简单,不用再关心底层的实现,减少了应用开发者在底层上的开发时间。当然本地视频的设置也是如此的简单。</p>
<h2>本地视频</h2>
<p>对于本地视频的配置,首先需要设置视频参数,包括分辨率、帧率、码率等,当设置的分辨率不被摄像头支持的时候,SDK会自动找到一个合适的分辨率来适配摄像头,但显示的仍然是指定的分辨率。<br>设置本地视频配置的方法:</p>
<blockquote>
<p>setVideoProfile:(AgoraRtcVideoProfile)profile</p>
<pre><code>swapWidthAndHeight:(BOOL)swapWidthAndHeight;
</code></pre>
</blockquote>
<p>第一参数包含了分辨率、帧率、码率的配置,在SDK中已经有已经设置好的参数,在demo中使用的是<code>._VideoProfile_360P</code>,第二个参数表示是否交换宽和高,用来适应横屏和竖屏的显示。默认为false。<br>设置本地视频配置:</p>
<blockquote><p>agoraKit.setVideoProfile(._VideoProfile_360P, swapWidthAndHeight: false)</p></blockquote>
<p>和远程视频的设置一样,首先需要一个view用来接收本地视频的显示,定义一个本地显示本地视频的view:</p>
<blockquote><p>@IBOutlet weak var localVideo: UIView!</p></blockquote>
<p>本地视频的显示也需要实例化一个<code>AgoraRtcVideoCanvas</code>,还要配置AgoraRtcVideoCanvas的三个参数,然后把<code>AgoraRtcVideoCanvas</code>设置给agoraKit,完整代码如下:</p>
<pre><code class="swift">func setupLocalVideo(){
agoraKit.setVideoProfile(._VideoProfile_360P, swapWidthAndHeight: false)
let videoCanvas=AgoraRtcVideoCanvas()
videoCanvas.uid=0
videoCanvas.view=localVideo
videoCanvas.renderMode = .render_Adaptive
agoraKit.setupLocalVideo(videoCanvas)
}</code></pre>
<p>上面完成了本地视频和远程视频的设置和显示,在需要和人一起聊天之前,还要两个人都加入到一个频道中。</p>
<h2>加入一个频道</h2>
<p><code>频道</code>:在同一个频道内的用户可以互相通话,如果多个用户加入了一个频道就可以群聊,一个用户只能加入一个频道。切换频道必须从当前频道中退出。<br>先看加入频道的代码:</p>
<pre><code class="swift"> func joinChannel(){
agoraKit.joinChannel(byKey: nil, channelName: "demo", info: nil, uid: 0){[weak self](sid,uid,elapsed)->Void in
if let weakSelf = self{
weakSelf.agoraKit.setEnableSpeakerphone(true)
UIApplication.shared.isIdleTimerDisabled = true
}
}</code></pre>
<p>下面简单说下各个参数的含义:</p>
<ul>
<li><p>byKey 可选参数,使用nil或者App ID都可以,如果对于安全要求极高的话可以使用申请Channel Key</p></li>
<li><p>channelName 频道名称</p></li>
<li><p>info 开发可以附件信息,该信息不会给用户看到</p></li>
<li><p>uid 用户唯一标识</p></li>
<li><p>joinChannelSuccessBlock 一个加入成功的回调block,在加入频道成功之后通过设置isIdleTimerDisabled来阻止用户锁屏。</p></li>
</ul>
<p>通过<code>agoraKit.leaveChannel()</code>可以离开频道,只有离开一个频道才能进入下一个频道,leaveChannel是异步操作,调用时并没有真正的退出频道,在真正的退出频道后,会触发didLeaveChannelWithStats回调。<br>在一个频道中的用户就可以正式的开始聊天了。<br>在聊天过程中特别是会议聊天时,有时需要禁止自己的声音,防止打扰别人说话,Agora SDK也提供了对于声音和摄像头的控制。</p>
<h2>声音控制</h2>
<p>聊天中对于声音的控制有很多种方式,下面介绍下几种比较常用的方式:</p>
<h4>声音开关</h4>
<p><code>muteLocalAudioStream </code>可以设置本地声音的开关,使用方式也比较简单,通过一个Button控制本地声音的开启:</p>
<pre><code class="swift">@IBAction func mute(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
agoraKit.muteLocalAudioStream(sender.isSelected)
}
</code></pre>
<p>而<code>muteAllRemoteAudioStreams </code>的作用是禁止所有的远程视频的声音,使用方式和<code>muteLocalAudioStream</code>一样。<br>如果想禁止某一个用户的声音可以可以使用方法:</p>
<pre><code>-(int)muteRemoteAudioStream:(NSUInteger)uid muted:(BOOL)muted;</code></pre>
<p>其中uid是用户的唯一标识,利用uid就可以针对某一个用户开启/关闭该用户的声音。</p>
<h4>开启扬声器</h4>
<p>通过方法:</p>
<pre><code>-(int)setEnableSpeakerphone:(BOOL)enableSpeaker;</code></pre>
<p>可以设置使用扬声器或听筒,其中YES是输出声音到扬声器,NO是使用听筒。</p>
<h4>监听声音</h4>
<p>在多人聊天的过程中,我们还需要知道是谁在说话,这个时候就需要设置监听用户的声音状态,通过方法:</p>
<pre><code>-(int)enableAudioVolumeIndication:(NSInteger)interval smooth:(NSInteger)smooth;</code></pre>
<p>就可以监听远程用户的声音状态,设置该方法之后可以在<code>AgoraRtcEngineDelegate</code>中的reportAudioVolumeIndicationOfSpeakers中收到谁在说话以及他说话的音量。</p>
<p>上面是针对声音的方法,更多的设置可以参考官方的文档,下面再看下针对摄像头的方法</p>
<h2>摄像头</h2>
<p>对于摄像头的控制方法也有很多的方式,下面以几个比较常用方法为主简单介绍下。</p>
<h4>开启本地预览</h4>
<p>在demo中的频道列表中使用的背景是本地视频的预览,本地预览相关的有两个方法:</p>
<blockquote><p>startPreview(开启预览)<br>stopPreview(停止预览)</p></blockquote>
<p>注意开启视频预览之前必须先设置本地的视频显示属性以及预览的UIView,详细的设置可以参考demo。</p>
<h4>摄像头切换</h4>
<p>视频聊天中常常需要对前置/后置摄像头进行切换,切换摄像头的代码为:</p>
<pre><code class="swift"> @IBAction func switchCamera(_ sender: UIButton) {
agoraKit.switchCamera()
}</code></pre>
<p>使用该方法,SDK会判断当前摄像头的状态,并对摄像头进行切换。</p>
<h4>视频开关</h4>
<p>和声音开关类似,也可以通过API暂停发送视频,暂定发送本地视频流的方法:</p>
<pre><code>-(int)muteLocalVideoStream:(BOOL)mute;</code></pre>
<p>暂停所有远程视频流的方法:</p>
<pre><code>-(int)muteAllRemoteVideoStreams:(BOOL)mute;</code></pre>
<p>暂停某一个远程用户视频的方法:</p>
<pre><code>-(int)muteRemoteVideoStream:(NSUInteger)uid mute:(BOOL)mute;</code></pre>
<p>通过该例子可以实现一对一的视频聊天,下一篇文章将介绍下如果进行多人视频聊天。</p>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=IBBHHHzkf%2Fz24RXUZlxd%2Bw%3D%3D.BtWB7iXBwhGjXj4D9okl7I8Q4gbvHryEBiu396nRwJbPwKbDrDpe1z%2FiPp6zLIcb" rel="nofollow">https://github.com/jjz/agora-swift</a><br>参考文档:<a href="https://link.segmentfault.com/?enc=fbhSt5yx4u5beiVYEcLXtQ%3D%3D.vpsHO305g%2BmJBeuoPhl%2FZ6lhyhS9H1LMTA78UiCUpyJb%2Fdfh32Sj9fTqoptOBsH88cNvkYhCX70NZUyN0vSg4g%3D%3D" rel="nofollow">https://docs.agora.io/cn/user_guide/API/ios_api.html</a></p>
AsicBoost和SegWit
https://segmentfault.com/a/1190000009262965
2017-05-02T16:03:38+08:00
2017-05-02T16:03:38+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>关于AsicBoost和SegWit的讨论已经冷清了很多,但我还是想从技术的角度尝试解释下:</p>
<ol>
<li><p>AsicBoost是什么</p></li>
<li><p>AsicBoost和SegWit有什么关系</p></li>
</ol>
<p>在说这两件事情之前,离不开一个关键词:<code>挖矿</code>,那就先来说说挖矿吧!</p>
<h2>挖矿</h2>
<p>比特币的挖矿机制:比特币挖矿机制采用的是SHA256算法,但SHA256算法并不是针对整个块做的,而是只是针对块头(Block Header)做SHA256,下图是block的组成:<br><img src="/img/remote/1460000009262968?w=500&h=293" alt="block" title="block"></p>
<p>从上图中可以看到Block hash是从哪些字段组合之后再做hash而得到的,其中黄色背景的字段就是块头,它包含:</p>
<blockquote><ol>
<li><p>版本号</p></li>
<li><p>上一个块的hash</p></li>
<li><p>Merkle root</p></li>
<li><p>timestamp(时间戳)</p></li>
<li><p>bits(难度)</p></li>
<li><p>Nonce (随机数)</p></li>
</ol></blockquote>
<p>在一轮挖矿过程中,其中的版本号、上一块的hash、难度都是确定的,矿工需要做的就是不断的修改Nonce,以改变当前块的hash值以找到小于当前难度的<code>Block Header</code>。<br>但Nonce的可用搜索空间是不够的,原因就是Nonce的位数只有<strong>4bytes</strong>。Block Header中各字段所占的位数:<br><img src="/img/remote/1460000009262969?w=540&h=301" alt="Block Header位数" title="Block Header位数"><br><strong>4bytes的Nonce</strong>的意味着他的搜索概率空间为<code>2^32</code>,也就4G次的hash运算就能遍历完,对于当前的单个矿机来说也就是一瞬间就可以完成的事。<br>在Nonce的搜索空间不够的情况下,就只剩下<code>timestamp</code>和<code>Merkle root</code>可以改变了,timestamp可以前后调整,但是调整之后的搜索空间还是不够。<br>矿工通过修改Coinbase交易、或者交易顺序、或者其他的方式,获取新的<code>Merkle Root</code>,再重新做2^32次Nonce的遍历。而<code>Merkle Root</code>是32 bytes,它的搜索空间足够大。<br>总结比特币的挖矿:</p>
<blockquote><p>简单来说比特币挖矿就是通过不断更改Nonce来改变块hash以寻找小于当前难度的Block Header,但是Nonce的搜索空间太小了,在做完2^32次哈希没有找到对应的块头就需要变更Merkle Root重新计算。</p></blockquote>
<p>上面说了简单说了下比特币挖矿机制,那<code>AsicBoost</code>又是怎么回事呢?</p>
<h2>AsicBoost</h2>
<p><code>AsicBoost</code>是和<code>SHA256的计算</code>、<code>Block Header结构</code>有关的一种算法,在开始计算块头hash的时候是需要补齐到128 bytes再做SHA256计算,而上面所示块头只有80 bytes,剩下的需要使用固定的48 bytes填充到128 bytes。<br>而在计算<code>128 bytes</code>的hash的过程是分两个过程进行的,前64 bytes一起计算,后64 bytes一起运算:<br><img src="/img/remote/1460000009262970?w=540&h=227" alt="块头SHA256计算" title="块头SHA256计算"><br>这样一个被填充过的块头就被分成了两个部分,比较有意思的是Merkle Root,Merkle Root的32个bytes中前28个bytes被放在前部分计算,后4个bytes被放在后部分计算,<code>Block Header hash</code>的计算公式为:</p>
<pre><code>SHA256=F(Chunk1)+B(Chunk2)
Chunk1=(version)+(Previous hash)+F28(Merkle root)
Chunk2=B4(Merkle Root)+Timetamp+Bits+Nonce+padding</code></pre>
<p>结合上面所述,块hash计算时就出现了一个现象:</p>
<blockquote><p>每次更改Nonce的值的时候,Chunk1的值保持不变,这意味着每次变更Nonce的时候只需要重新计算<code>B(Chunk2)</code>再结合上一次计算的<code>F(Chunk1)</code>即可。</p></blockquote>
<p><strong>这是一种优化挖矿的方法</strong>,优化之后每一轮在可搜索空间中变更Nonce,计算SHA256的公式就变成了:</p>
<blockquote><p>SHA256=F(Chunk1)(不变)+B(Chunk2)`</p></blockquote>
<p><strong>基本上所有的矿机都做了这个优化</strong>。而<code>AsicBoost</code>在这个优化的基础上又延伸了思路,找到了另一种优化方法:</p>
<blockquote><p>既然可以保持<code>Chunk 1</code>不变,有没有办法保持<code>Chunk 2</code>不变呢?从前面的公式中可以看到只要保持<code>Merkle Root</code>的最后4位、时间戳、和Nonce不变的情况下即可保持<code>Chunk 2</code>不变。</p></blockquote>
<p>如果能够找到<code>Merkle Root</code>后四位是相同的话,那么在同一个timestamp和Nonce不变的情况下,就可以得到另一个优化公式:</p>
<blockquote><p>SHA256=F(Chunk1)`+B(Chunk2)(不变)</p></blockquote>
<p>对于timestamp来在一轮挖矿过程中它基本是不变的,而Nonce是在2^32内搜索空间内遍历的,剩下的问题就是要找到足够多的后四位相同的<code>Merkle Root</code>,这样在每次遍历Nonce时就可以复用后部分的计算结果,就有效的减少了计算,提高了找到块hash的概率。<br>前面说过可以通过改变交易顺序、更改Coinbase等方式得到新的Markle Root,这样就可以通过碰撞找到后4位相同的Merkle Root,那通过碰撞找到后4位相同的hash的概率是多少呢?根据"生日悖论"(后4位相同的bytes就是32 bits相同的概率),它的概率是:</p>
<p><img src="/img/remote/1460000009262971?w=540&h=162" alt="碰撞概率" title="碰撞概率"></p>
<p>大概碰撞77000次就有50%的概率会出现后四位相同的hash,而这样的碰撞能提高多少概率呢?AsicBoost白皮书中给出现的结果:<br><img src="/img/remote/1460000009262972?w=540&h=84" alt="碰撞次数与概率" title="碰撞次数与概率"><br>这种优化理论上能够提高20%的碰撞效率,而合并的性能提升大概是7%左右。<code>AsicBoost</code>可以在软件上实现,也是通过芯片(硬件)上实现。变更<code>Merkle Root</code>的方法:</p>
<ol>
<li><p>修改Coinbase交易,白皮书认为不够高效</p></li>
<li><p>另一种就是更新Merkle树的排序</p></li>
<li><p>...其他方式</p></li>
</ol>
<p>可以看出<code>AsicBoost</code>是<strong>一种基于比特币块头和SHA256算法做出的优化</strong>,并不是一种攻击。</p>
<h2>AsicBoost只有一种技术优化</h2>
<p>很明显AsicBoost既没有破坏现在的比特币协议,也没有生产出不可用的块,更不会出现针对比特币的安全问题。<br>而基于SHA256算法的优化在比特币历史中也出现了好几次:</p>
<ol>
<li><p>前边提到的变更Nonce的时候,前半部分F(Chunk1)并不需要重新计算</p></li>
<li><p>后部分的前三轮也是可以优化的参考<a href="https://link.segmentfault.com/?enc=%2FZ8nCzDd6Tr7iyVVHkOsaA%3D%3D.CR%2FEIq6eB5wMRcEqa1jo2Zuo6XPYc0b4uBW4oDOgumvLeDylVO%2FrU1ukBB7x93bwsDyQXZ%2BklbQVwY%2BaNRTCaCHM0DNWPL6YYWGeihWU1Gs%3D" rel="nofollow">ms3steps</a></p></li>
<li><p>......</p></li>
<li><p>AsicBoost</p></li>
</ol>
<p>可以这样说所有的软件和系统都存在被优化的可能性,比特币的挖矿历史就是一部不断优化效率的过程。<br>究竟我们应该如何定义<code>优化</code>和<code>攻击</code>呢?这是一个值得思考的问题。优化SHA256前64位的计算是允许的,优化后64位的计算就是<code>攻击</code>了吗?</p>
<blockquote><p><code>AsicBoost</code>是一个优化算法,只是在原有的比特币挖矿基础上提高了碰撞hash的概率,用来找到更合适的<code>Block Header</code>,提高了找到块头的概率,并不是漏洞</p></blockquote>
<p>如果存在一种提升比特币挖矿效率的技术,我更希望矿工早日应用上这种技术,这样攻击者和矿工相比就不存在技术优势。毕竟算力才是比特币安全的基础,攻击者在技术上领先矿工的话,比特币被攻击的可能性会增加很多。</p>
<p>介绍完了<code>AsicBoost</code>,再来看下<code>AsicBoost</code>和SegWit又有什么样的关系呢?</p>
<h2>SegWit和AsicBoost</h2>
<p>SegWit(Segregated Witness)即隔离验证,它的应用将会对TX有所改变,它将会采用一种新的TX ID:<code>Witness ID</code></p>
<p><img src="/img/remote/1460000009262973?w=540&h=69" alt="witnesstx" title="witnesstx"><br>相应地 Witnesss ID就对对应有<code>Witness Merkle Tree</code>,继而就有了<code>Winess Merkel Root</code>,而<code>Winess Merkel Root</code>写在哪里呢?答案就是<code>Coinbase</code>。<br>在<code>SegWit</code>协议中Coinbase会增加一个新的output,新的output为:</p>
<pre><code>output_data = WITNESS_COMMITMENT_HEADER + ser_uint256(uint256_from_str(hash256(ser_uint256(witness_root)+ser_uint256(witness_nonce))))
script = CScript([OP_RETURN, output_data])</code></pre>
<p>新增的output包含:OP_RETURN+WITNESS信息+<code>Witness Merkle Root</code>的hash组成的Script。<br>而<code>Witness Merkle Root</code>的计算是不包括<code>Coinbase</code>的,这样就避免了Coinbase和<code>Witness Merkle Root</code>相互改变而造成的死循环。</p>
<p>这样就出现了一个问题,就是如果在<code>SegWit</code>中变更任意交易位置的话就会导致<code>Witness Merkle Root</code>的变化,而Coinbase中是要包含<code>Witness Merkle Root</code>的信息,这样就会影响Coinbase的变化,Coinbase的变化会导致整个块的Merkle Root发生了变化。<br>如果在SegWit中使用的AsicBoost是通过变更交易顺序获取新的Merkle Root了,效率就会降低,因为需要同时计算<code>Witness Merkle Root</code>和<code>Merkle Root</code>,进而降低<code>AsicBoost</code>的效率。<br>这就是SegWit对于AsicBoost的影响。但不能忽略了一条重要的事实:<code>SegWit</code>和<code>AsicBoost</code>并不是互斥的:</p>
<blockquote><p>只要块头结构不变的情况下<code>AsicBoost</code>的优化仍然存在,仍然有效。</p></blockquote>
<p>在SegWit中变更Coinbase获取<code>Merkle Root</code>的方式和当前协议中变更Coinbase的效果是一样的,因为<code>Witness Merkle Root</code>中并不包括Coinbase TX。<br>SegWit并不是和AsicBoost是互斥的,并不是在SegWit中就不存在AsicBoost的优化了。而在<code>SegWit</code>中使用<code>AsicBoost</code>在工程上也是可以进行优化的:计算Merkle Root也是要计算hash的,可以在计算Merkle Root不时阻塞块hash的计算,并行计算会是一种更好的优化方式,在并行的情况下使用变更<code>Coinbase</code>获取<code>Merkle Root</code>所带来的效率的降低并不会特别明显。</p>
<h2>总结</h2>
<p>AsicBoost的原理:</p>
<blockquote><p>在计算块头的时候,Merkle Root被分割成了两个部分计算,导致了如果使用后4位相同的Merkle Root去计算块hash的话,会提高挖矿的效率</p></blockquote>
<p>SegWit:</p>
<blockquote><p>SegWit 需要使用<code>Wintess TX ID</code>,继而有了新的<code>Witness Merkle Root</code>,而<code>Witness Merkle Root</code>是会写进Coinbase的,Coinbase本身是不会写入<code>Witness Merkle Root</code>的。因为软分叉的原因,块头的结构并没有变化。</p></blockquote>
<p>根据上面所述可以得出以下结论:</p>
<h3>AsicBoost本质上只是基于块头结构和SHA256算法其中的一种优化</h3>
<h3>AsicBoost和SegWit并不互斥</h3>
<p>块头结构和SHA256算法不变的前提条件,<code>AsicBoost</code>会一直存在。</p>
<h3>SegWit会对于AsicBoost中交易互换的方式有影响</h3>
<p>在SegWit中,每次的变更交易顺序都会导致Coinbase的变化,继而需要重新计算<code>Merkle Root</code>。交易顺序的变更会导致<code>Witness Merkle Root</code>和<code>Merkle Root</code>的变化。</p>
<h3>如果有更好工程化地优化AsicBoost的方式,在SegWit中仍然有效</h3>
<p>除了变更交易顺序更新Merkle Root的方式效率会降低以外,使用工程化优化AsicBoost的方式仍然可以有效。比如并行计算等方式</p>
<p><code>AsicBoost</code>只是一种优化挖矿的方式,而且在SegWit中<code>AsicBoost</code>优化也没有消失,因为块的结构并没有改变。G Maxwell在邮件中提出了一种更改块头的方式让<code>AsicBoost</code>不能再继续使用,我并不反对这种提议,只是认为没有这个必要,如果不允许矿工优化后64为bytes的计算,那前64位bytes的优化计算是不是也应该想办法禁止掉呢?而且未来说不好还会出现其它类似的优化,难道都要禁止掉吗?</p>
<blockquote><p>在给定的条件下,人类总是有办法找到方法A优于原来的方法B的,人类的历史就是效率不断被提高的历史</p></blockquote>
<p>分享看到的一个<a href="https://link.segmentfault.com/?enc=a6dcuMTIkFpsoNydl6pzPA%3D%3D.WzfY%2BXcTl0C%2BGWV3QGhiITycnp4RnC%2BUadBPMmRAFxk1IyvLENGXbQRrncB6Ksra" rel="nofollow">微博</a>,分享下:<br><img src="/img/remote/1460000009262974?w=598&h=163" alt="微博截图" title="微博截图"></p>
Agora iOS SDK-快速入门
https://segmentfault.com/a/1190000009024365
2017-04-11T15:25:42+08:00
2017-04-11T15:25:42+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>最近有一款应用很火,叫<code>Housparty</code>,Housparty是一款视频群聊应用,最近它在App Store上的下载排名已经超过了Facebook。同时,有媒体称Houseparty已经完成了最新一轮的投资。自己就想着模仿一个玩玩,研究了下,发现有个叫<code>Agora</code>的可以实现部分的功能,就想试用下。<br><code>声网Agora.io</code>是一家提供稳定,高可用,有质量保障的实时<code>视频通话</code>和实时<code>全互动直播技术</code>服务的平台,支持全平台,只需要简单集成SKD,即可让应用实现高清视频通话,和多主播实时全互动直播。<br>既然已经有了SDK,就不需要自己写视频以及通信部分的代码了,这样写一个<code>Housparty</code>的demo就更简单了,下面以iOS平台为例,看下如何集成<code>Agora SDK</code>。</p>
<h2>环境准备</h2>
<p>本文使用的环境如下:</p>
<ul>
<li><p>XCode 8.3.1</p></li>
<li><p>最小SDK iOS 9.3</p></li>
<li><p>真机</p></li>
<li><p>Swift语言</p></li>
<li><p><a href="https://link.segmentfault.com/?enc=b2UtxG9Zqcm6doDnJg6PEA%3D%3D.GxV%2BzT4AkxKAZ21MyczRvFny4oSJeIyEuisoHXNxU%2FHHFmvo1f%2Bg5iH5e30mnAR4" rel="nofollow">最新Agora SDK </a></p></li>
<li><p><a href="https://link.segmentfault.com/?enc=YQpNdSUFTMnzbyVc%2BnXphg%3D%3D.HsfGuWZeOMaMBKF5azkouchtRB64oMUfol1Y%2BZoBljsEPY5OUNVUukxHV9UY1aLR" rel="nofollow">申请AppID</a></p></li>
</ul>
<p>先要注册<code>agora.io</code>,注册完成之后再新建一个项目,需要拿到对应的<code>App ID</code>。后面的示例代码需要使用该<code>App ID</code>。<br>最新的<code>Agora SDK</code>中有两个文件夹:</p>
<ul>
<li><p>./libs 中含有的是所需的库(集成到App需要的)</p></li>
<li><p>./samples 包含Open Video Call 和 Open Live的代码示例</p></li>
</ul>
<p>需要的环境准备好之后,新建一个<code>agora</code>项目,就可以开始配置和集成<code>Agora SDK</code>。</p>
<h2>添加SDK</h2>
<p>先把Agora SDK中<code>./libs</code>复制到agora项目中的agora目录下。再把Agora SDK添加到项目的<code>Libraries</code>中。<br>添加<code>Libraries</code>的方法:</p>
<ol>
<li><p>选中当前Target(agora)</p></li>
<li><p>Build Phases</p></li>
<li><p>Link Binary With Libraries</p></li>
<li><p>点击+</p></li>
</ol>
<p><img src="/img/remote/1460000009024368" alt="Link Binary" title="Link Binary"><br>这个时候出现的界面是添加系统类库的,选择<code>Add Other ...</code>从项目目录中选中<code>./libs</code>添加里面的内容到<code>Libraries</code>中。这样就添加了<code>Agora SDK</code>。<br>在添加<code>Agora SDK</code>之后,还需要添加<code>Agora SDK</code>所依赖的库,参考官网上面demo使用的<code>libraries</code>,添加以下类库:</p>
<ul>
<li><p>CoreTelephony.framework</p></li>
<li><p>CoreMedia.framework</p></li>
<li><p>VideoToolbox.framework</p></li>
<li><p>AudioToolbox.framework</p></li>
<li><p>AVFoundation.framework</p></li>
<li><p>libc++.tbd</p></li>
</ul>
<p><code>build</code>的时候出现错误:</p>
<pre><code>Showing All Messages
"_res_9_getservers", referenced from:
agora::commons::network::get_dns_list(bool) in AgoraRtcEngineKit(libmediasdk.a-arm64-master.o)
"_res_9_ninit", referenced from:
agora::commons::network::get_dns_list(bool) in AgoraRtcEngineKit(libmediasdk.a-arm64-master.o)
"_res_9_ndestroy", referenced from:
agora::commons::network::get_dns_list(bool) in AgoraRtcEngineKit(libmediasdk.a-arm64-master.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
</code></pre>
<p>运行官网的demo也出现了同样的错误,说明类库没有导入完整,还需要导入类库是<code>libresolv.9.tbd</code>。<br>类库依赖问题比较容易出现,另一方面也说明没有支持<code>Pod</code>的麻烦,建议<code>Agora</code>能尽快支持<code>CocoaPods</code>。<br>再次<code>build</code>又出现了个问题:</p>
<pre><code>ld: library not found for -lcrypto</code></pre>
<p>问题的原因是:编译时找不到需要的链接库导致的,解决方法:</p>
<ol>
<li><p>选中当前Target(agora)</p></li>
<li><p>Build Settings</p></li>
<li><p>Search Paths</p></li>
<li><p>Library Search Paths</p></li>
<li><p>新增<code>"${PROJECT_DIR}/agora/libs"</code></p></li>
</ol>
<p><img src="/img/remote/1460000009024369" alt="Library Search Paths" title="Library Search Paths"><br>另外<code>Agora SDK</code>并不支持<strong>bitcode</strong>,因此需要把bitcode禁止掉,禁止bitcode的方法:</p>
<ol>
<li><p>选中当前Target(agora)</p></li>
<li><p>Build Settings</p></li>
<li><p>Build Options</p></li>
<li><p>Enable Bitcode ->No</p></li>
</ol>
<p><img src="/img/remote/1460000009024370" alt="bitcode" title="bitcode"></p>
<p>这样就配置完成了<code>Agora SDK</code>的支持,下面就可以开始使用<code>Agora SDK</code>了。</p>
<h2>访问OC类库</h2>
<p><code>Agora SDK</code>使用的是<code>Object-c</code>开发的,而我们的项目使用的是<code>Swift</code>开发的,因此就需要在<code>Swift</code>中访问<code>OC的类库</code>,新建一个文件命名为<code>agora-Bridging-Header.h</code>,在该文件中引入<code>Agora SDK</code>:</p>
<pre><code>#import <AgoraRtcEngineKit/AgoraRtcEngineKit.h>
#import <AgoraRtcCryptoLoader/AgoraRtcCryptoLoader.h></code></pre>
<p>然后把该文件设置为<code>Objective-C Bridging Header</code>,设置方法:</p>
<ol>
<li><p>选中当前Target(agora)</p></li>
<li><p>Build Settings</p></li>
<li><p>Swift Compiler-General</p></li>
<li><p>Objective-C Bridging Header</p></li>
<li><p>agora/agora-Bridging-Header.h</p></li>
</ol>
<p><img src="/img/remote/1460000009024371" alt="oc bridging" title="oc bridging"><br>这样能在<code>Swift</code>中使用<code>Agora SDK</code>了。<br>在开始调用<code>Agora SDK</code>之前还要知道,<code>Agora SDK</code>实现的是一套高清视频通话或直播系统,除了完成集成<code>Agora SDK</code>的工作之外,还需要给项目对应的访问权限。</p>
<h2>权限</h2>
<p>需要给项目两个权限才能使用<code>Agora SDK</code>,这两个权限是:</p>
<ul>
<li><p>相机</p></li>
<li><p>麦克风</p></li>
</ul>
<p>在<code>info.plist</code>添加这两个权限:<br><img src="/img/remote/1460000009024372" alt="info.plist" title="info.plist"><br>具体设置的内容为:</p>
<ul>
<li><p><code>Privacy - Camera Usage Description</code>设置为:<code>use camera to start video call</code></p></li>
<li><p><code>Privacy - Microphone Usage Description</code>设置为<code>use microphone to start video call</code></p></li>
</ul>
<p>这样完成了<code>Agora SDK</code>的项目配置,后面将继续写如何使用<code>Agora SDK</code>,包含的功能:</p>
<ol>
<li><p>创建room</p></li>
<li><p>分屏,2分屏、4分屏、6分屏</p></li>
<li><p>窗口切换</p></li>
<li><p>前后摄像头切换</p></li>
</ol>
<p>项目地址:<a href="https://link.segmentfault.com/?enc=2HNO4oz%2FzwsUzibUC7irTQ%3D%3D.BjGQ1JM0pVIY1DEdj9PvZ914fs1HgdMlZGYkMIRCNye5wf%2Buvh8DE03HriKrQbl5" rel="nofollow">https://github.com/jjz/agora-swift</a></p>
Ubuntu上安装Bitcoin Unlimited
https://segmentfault.com/a/1190000008802131
2017-03-23T12:56:58+08:00
2017-03-23T12:56:58+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>以前写过一篇<a href="https://link.segmentfault.com/?enc=BwNwcGY4uEsQsB%2BJBwF4zw%3D%3D.KK2zwJHH5rVSwYNvsmUW7AjFn0DzDBDytNJPrbGbmD280LJCuKaXM%2FqOaCRlb7tH" rel="nofollow">Ubuntu上安装Bitcoin Core</a>的文章,当前比特币扩容之争还在继续,今天决定把Ubuntu上面的<code>Bitcoin Core</code>换成<code>Bitcoin Unlimited</code>。下面对安装<code>Bitcoin Unlimited</code>的过程做个总结。</p>
<h2>安装前准备</h2>
<p>安装前需要先备份好数据。</p>
<ul>
<li>
<p>备份好你们的钱包,备份好你的私钥</p>
<pre><code>
>bitcoin-cli backupwallet /tmp/backp.dat
</code></pre>
</li>
<li><p>备份好<code>~/.bitcon</code>目录的数据,这里存放的是全部的区块数据,如果区块数据丢失或者损坏,需要重建索引,或者需要重新下载。</p></li>
<li><p>停止运行<code>bitcoind</code>:</p></li>
</ul>
<blockquote><p>bitcoin-cli stop</p></blockquote>
<p>备份好数据以及停止了<code>bitcoind</code>,下一步需要移除<code>Bitcoin Core</code>。</p>
<h2>移除Bitcoin Core</h2>
<p>之前有安装Core或者Classic的,需要先移除他们,<code>Bitcoin Unlimited</code>也是使用的<code>bitcoind</code>,不移除就无法正确安装。<br>删除客户端:</p>
<blockquote><p>sudo apt-get remove bitcoin*</p></blockquote>
<p>如果使用了的是<code>PPA</code>安装的还需要移除对应的bitcoin的<code>source</code>,<code>PPA</code>源的保存文件在<code>/etc/apt/sources.list.d</code>目录下,名称为:<code>bitcoin-bitcoin-trusty.list</code>,删除源命令:</p>
<blockquote><p>sudo rm /etc/apt/source.list.d/bitcoin-<em>.</em></p></blockquote>
<p>如果你是使用的源代码安装的<code>Bitcoin Core</code>,就需要使用<code>make</code>命令卸载:</p>
<blockquote><p>cd /path/where/the/code/is/stored<br>sudo make uninstall</p></blockquote>
<p>移除<code>Bitcoin Core</code>之后就可以安装<code>Bitcoin Unlimited</code>了。</p>
<h2>PPA安装Bitcoin Unlimited</h2>
<p>使用<code>PPA</code>安装<code>Bitcoin Unlimited</code>也是比较简单的,先添加Bitcoin Unlimited的源,然后直接安装即可:</p>
<blockquote><p>sudo add-apt-repository ppa:bitcoin-unlimited/bu-ppa-nightly<br>sudo apt-get update<br>sudo apt-get install bitcoind</p></blockquote>
<p>安装成功之后,使用和<code>Bitcoin Core</code>相同的命令就可以启动<code>bitcoind</code>了:</p>
<blockquote><p>bitcoind --daemon</p></blockquote>
<p>使用<code>bitcoin-cli</code>可以查看运行情况:</p>
<blockquote><p>bitcoin-cli getinfo</p></blockquote>
<p>上述安装过程我写了一个脚本,地址<a href="https://link.segmentfault.com/?enc=avQxWaADYUv5yWEpwrBGig%3D%3D.aSzzBWoTUG%2FIVc1h4q8a490KvXKfc7NaRe162Rbs2a1qtEs%2BCYSo9GMvEkRPDl%2FVTA4PZKaoGucPr1Vuk0au9w%3D%3D" rel="nofollow">https://github.com/jjz/script/blob/master/install_bu.sh</a></p>
比特币的私钥,公钥和地址是什么?
https://segmentfault.com/a/1190000008741956
2017-03-18T12:46:14+08:00
2017-03-18T12:46:14+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>在比特币中,经常出现三个词:<code>私钥,公钥和地址</code>。他们是什么意思呢?他们之间又有什么样的关系呢?搞清楚他们之间的关系和区别,是了解比特币的基础。</p>
<h2>私钥</h2>
<p>先说说私钥,一般我们看到的私钥是下面这样的一段字符串:</p>
<blockquote><p>5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss</p></blockquote>
<p>支持比特币协议的应用都可以正确把这段字符串转换成比特币的<em>私钥</em>,再转换成公钥,就可以得到一个地址,如果该地址上面对应的比特币,就可以使用这个私钥花费上面的比特币。</p>
<ul><li>
<p>私钥本质上是随机数</p>
<pre><code>私钥本质上是一个随机数,由32个*byte*组成的数组,1个*byte*等于8位二进制,一个二进制只有两个值0或者1。所以私钥的总数是将近2^(8*32)=2^256个,但是有一些私钥并不能使用,他真实的大小是介于:`1 ~ 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4141`之间的数。这个数量已经超过了宇宙中原子的总数,想要遍历所有的私钥,耗尽整个太阳的能量也是不可能的。
</code></pre>
</li></ul>
<p>我们所说的比特币私钥的是密码学上面安全的,并不是说不可能出现重复的私钥,而是说不可能通过遍历的方式找到某一个特定的私钥,或者通过其它的方式,不通过私钥就能花费地址上面的比特币,私钥的安全性是由数学上保证的。<br>私钥的总数量很大,但是私钥的生成是依赖随机数的,真正的随机是很难做到的,大部分私钥的生成都是依赖于<code>伪随机算法(PRNG)</code>。</p>
<blockquote><p>伪随机是用函数生成随机数。它并不真正是随机的。只是一个比较近似z真随机的随机数。</p></blockquote>
<p>私钥生成的随机性就很重要的,密码学上面安全的随机是指:</p>
<blockquote><p>随机是不可预测的,随机的结果是不可遍历的,如果不是安全的随机数生成器,生成的私钥就会被别人碰撞到。不依赖随机生成的私钥就会大大的降低生生成的概率空间。</p></blockquote>
<p>公钥和地址的生成都依赖私钥,所以我们只需要保存私钥即可,有了私钥就能生成公钥和地址,就能够花费对应地址上面的比特币。</p>
<ul><li><p>私钥到字符串<br>上面提高的私钥字符串是按照一定的规律从32位byte数据格式化生成的,32个byte的数组是由256个0或者1组成的,如果显示出来,不仅仅是识别率不高,而且私钥太长。</p></li></ul>
<p>因此私钥字符串就是对于原始的随机数进行一定的转换,转换为识别率高的形式,上面私钥的是对32个byte数组就做了<code>Base58</code>的转换.</p>
<blockquote><p>Base58是用于比特币中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址和私钥。相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+"和"/"符号。这样做的主要原因是为了肉眼容易识别,在输入的时候不容易打错,</p></blockquote>
<p>不过上面那段没有规律的字符串输入起来还是挺费劲的,当然也可以把私钥转换为单词的形式(12或者24个单词),设置是脑钱包的形式,又自己记住的某一句话来生成私钥,注意这种方式生成的私钥随机的安全性并不高。</p>
<p>我们看到的私钥除了以5开头的以外,还有以<code>L</code>和<code>K</code>开头的私钥,为什么会出现这样的情况呢?<code>5,L,K</code>又带代表什么呢?这就要说到公钥了。</p>
<h2>公钥</h2>
<p>比特币的根基是<code>椭圆曲线数字签名算法</code>:</p>
<blockquote><p>椭圆曲线数字签名算法(ECDSA)是使用椭圆曲线密码(ECC)对数字签名算法(DSA)的模拟</p></blockquote>
<p><code>椭圆曲线加密法(ECC)</code>是一种公钥加密技术:</p>
<blockquote><p>ECC以椭圆曲线理论为基础,利用椭圆曲线等式的性质来产生密钥,而不是采用传统的方法利用大质数的积来产生,其特点是:密钥长度小,安全性能高,整个数字签名耗时小。</p></blockquote>
<p>DSA(DigitalSignature Standard)数字签名技术:</p>
<blockquote><p>在DSA数字签名和认证中,发送者使用自己的私钥对文件或消息进行签名,接受者收到消息后使用发送者的公钥来验证签名的真实性。</p></blockquote>
<p>这里明确了<code>私钥</code>用来<code>签名</code>,而<code>公钥</code>用来<code>验证签名</code>。<br>公钥是由私钥生成的,通过<code>椭圆曲线(ECPoint)</code>生成,一个私钥经过<code>椭圆曲线</code>变换之后能够得到公钥,是一个65个byte数组,一般我们会看到这样的一个公钥:</p>
<blockquote><p>04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235</p></blockquote>
<p>显示出的公钥一般把byte数组是经过<code>hex(16进制)</code>的处理之后显示,不同于私钥的Base58, 公钥是用来验证私钥的签名,一般我们很少会看到公钥,使用私钥签名交易之后,会把自己的公钥一起和交易发送出去,这样对于一个完整的交易开说,他就使用交易里包含的公钥验证私钥的签名。<br>私钥和公钥是成对出现的,一个私钥签名的数据,只有对应的公钥才能对其进行验证,而地址也是从公钥生成的,这样就可以验证花费的交易是不是属于这个地址了?</p>
<ul><li><p>回答下上面的问题为什么会出现5开头或者L,K开头的私钥?<br>出现这种情况是因为公钥的不同格式而产生了不同的私钥格式,早期的比特币开发者没有使用压缩的公钥(椭圆曲线是对称的,知道了一半的信息就可以推导出来另外一般的信息),因此只需要保存一般的公钥信息即可。压缩的公钥只有33个byte,而未压缩的公钥有65个byte。</p></li></ul>
<blockquote><p>私钥开头的第一位的不同,是用来区分该私钥使用的公钥是否支持压缩格式</p></blockquote>
<p>压缩的公钥对比特币的意义很大,比特币是去中心化的<code>p2p加密货币</code>,每个节点都会拥有完整的交易记录,除了<code>coinbase</code>(挖矿得到的比特币)以外,每个交易都需要发送公钥,支持压缩格式的公钥,每个交易的数据就可以减少32个字节,这对整个比特币网络是非常有意义的,整个比特币网络的数据在传输和保存中都可以节省不少。</p>
<ul><li><p>而对私钥进行Base58编码的时候,老版本未压缩公钥的私钥是33位byte数组,第一位存放私钥的<code>Version信息</code>,当前值为128,生成的Base58都是以5开头。</p></li></ul>
<blockquote><p>老版本未压缩私钥=Base58(version+32位随机数)</p></blockquote>
<ul><li><p>支持压缩公钥的私钥是34位,同样是第一位是version信息,它的值也是128,而多出来的一位是最后一个byte是用来存放<code>是否压缩信息的信息</code>,1就表示是支持<code>压缩格式的公钥</code>。经过Base58处理之后正好是L或者K开头</p></li></ul>
<blockquote><p>新版本私钥格式=Base58(version+32位随机数+是否支持压缩)</p></blockquote>
<p>例子中的私钥不仅仅包含了32个byte数组的信息,还是私钥version的信息以及其公钥是否压缩的信息(通过位数)<br>公钥是否压缩除了对私钥的显示有影响以外,还会对地址有影响。</p>
<h2>地址</h2>
<p>公钥太长了,所以就有更短一些的地址的概念,另一方面没有发送过交易的地址,并不想暴露自己的公钥,而地址是通过<code>摘要算法</code>生成的,不会暴露公钥:</p>
<blockquote><p>地址是由公钥产生的,地址长度为25byte,经过base58处理,地址未尾添加了4个字节的校验位。</p></blockquote>
<p>我们看到的地址一般都是<code>Base58</code>编码处理的,地址的生成比较复杂,公钥到地址生成的过程是,先对公钥做一次<code>SHA256</code>(哈希算法)。</p>
<blockquote><p>sha-256-hash= SHA-256(public key)</p></blockquote>
<p>再经过了<code>hash160</code>处理, hash160,RIPEMD(PACE integrity Primitives Evaluation Message Digest)是一种原始完整性校验消息摘要,160标准对应20字节</p>
<blockquote><p>hash160=hash160(sha-256-hash)</p></blockquote>
<p>对结果进行hash160处理可以得到一个20个byte的数组,在这个20位的byte数组前面再加上一个byte,这个byte就是地址的Version信息,地址的Version当前值为0,Version信息在比特币的test网络上会使用不同的值,比特币地址完成的表示就是:</p>
<blockquote><p>address=Base58(version+hash160(SHA-256(public key))+checksum)</p></blockquote>
<p>checksum是用来对于比特币地址进行检验的,再得到的hash160中加入地址的version信息 ,再对该信息做两次<code>SHA-256</code>之后取前4位就是checksum:</p>
<blockquote><p>checksum=get_front_four( SHA-256(SHA-256(version+hash160)))</p></blockquote>
<p>其中hash160是这个过程中最重要的信息,从这个值就可以到地址的前21位(第一位是version)和后面的checksum, 进而可以生成Base58格式的地址。而从Base58格式的地址中也可以得到hash160,也就是说hash160可以和Base58格式的地址互换。</p>
<p>前面已经说了:</p>
<blockquote><p>一个随机数可以有一个压缩的公钥,和一个未压缩的公钥</p></blockquote>
<p>而每个公钥都会生成一个地址,上面私钥其实可以有两个地址分别为:</p>
<blockquote><p>1HZwkjkeaoZfTSaJxDw6aKkxp45agDiEzN(未压缩公钥)</p></blockquote>
<p>1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV (压缩公钥)</p>
<p>这两个地址都是这一个私钥的随机数生成的地址,每个地址上面的比特币都可以用这个对应的私钥花费。目前大多数的应用默认都使用压缩格式的私钥。</p>
<p><code>公钥</code>作为私钥到地址的中间桥梁,他在交易的验证是最关键的:</p>
<blockquote><p>对于一个交易的验证,公钥的作用:</p></blockquote>
<ol>
<li><p>公钥生成地址,验证发送交易的地址是否和该公钥生成的地址一致</p></li>
<li><p>公钥验证私钥的签名,用来验证该交易是否使用了正确的私钥签名</p></li>
<li><p>私钥生成公钥是成对出现,公钥可以生成对应的唯一地址,这样就能确认了该地址发送的交易是否使用了对应的私钥。</p></li>
</ol>
比特币将取代黄金
https://segmentfault.com/a/1190000008713411
2017-03-16T10:05:10+08:00
2017-03-16T10:05:10+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>《黑天鹅》一书的作者纳西姆·尼古拉斯·塔勒布说过:“比特币必须经历被若干政府禁止、被政客攻击的过程,否则就不配成功”。比特币会一直受到政府的阻止,政客的攻击。<br>比特币的价值究竟如何?最容易做类比的是黄金。而比特币vs黄金,比特币将会胜出,比特币的总市值会超越黄金的总市值。<br>黄金作为稀有金属,总量恒定。黄金主要的作用:<code>工业上面的使用,首饰,财富的储存</code>。<br>黄金在工业上的使用比重是很小的,黄金目前最大的使用是在首饰上约占总量的70%,首饰上的大量使用其实也是源自于黄金的稀有和稳定性,是一种财富的象征。 <br>黄金可以用来储存财富,以应对通货膨胀。而面对通货膨胀,我们又有了另外的选择来储存财富:<code>比特币</code>,比黄金还要优秀。</p>
<blockquote><p>为什么比特币是优于黄金呢?</p></blockquote>
<h2>黄金是你自己的吗?</h2>
<p>先看一条央行的规定:</p>
<pre><code>
个人携带200克以上黄金进出境或需报税</code></pre>
<p>也就是黄金的进出境是有比较严格的规定的,对应前些日子的一个报道:携带6斤"土豪金"出境 惨遭罚没,还被判刑(缓刑)。存储财富的第一条是流通性,国内外对于黄金的流通都是有限制的,目前合法的黄金交易场所是<em>银行</em>和<em>黄金交易所</em>。<br>而比特币完全不一样,比特币是去中心化的现金,只要拥有私钥,就拥有比特币,没有人能够抢走你的财富,甚至都不知道你拥有比特币,你自己拥有的比特币就是自己的。而比特币的流通性也是高效的,可以到达全世界任意的位置只需要10分钟(平均时间),且没有任何人可以阻止。只需要极低的手续费即可实现。</p>
<pre><code> 只要自己保管好自己的私钥,不管你是把私钥放在电脑,手机,u盘,甚至是纸上,或者你得记忆力比较好,自己把私钥给背下来,不管你到世界的那个角落,只要你只私钥,你就能完全使用自己的比特币。你不用担心央行,不用担心海关。</code></pre>
<h2>鉴别成本</h2>
<p>黄金在鉴别的成本是高昂的,而比特币是的鉴别成本极低,普通人完全无法鉴别黄金的真伪(影视剧中用牙咬的难道就不担心重金属中毒吗?)只有借助专业的工具才能知道黄金的含量,才知道它真正的价值。而比特币的鉴别成本基本为0,全网算力来保证你的币就是你的。只需要知道自己的地址,就可以在支持比特币协议的网站上查到你的余额。而证明比特币是自己的也简单,只需要使用私钥对任意一段信息进行签名,别人就能知道你是否拥有该地址的私钥。</p>
<h2>黄金不容易找零</h2>
<p>一个金块你不可能随便的切割,拿一块金子买一根葱,你让别人怎么找你钱呢?在历史上黄金也是用于价值存储的比较多,真正在交易过程中使用的反而少,大宗交易除外。<br>目前大家常用的黄金的计量单位是克,但是这个并不是真正能使用的单位,因为不能把黄金随意的切割到克,大部分黄金的存在都是以首饰,金块的方式存在。<br>而比特币不一样,比特币当前最小的单位是1聪,是一个比特币的一亿分之一,比特币的使用可以随意的细分,比特币是互联网协议,是一个巨大的账本,比特币发生转移就是账本的记录改变而已,如果需要更小的比特币单位,还可以通过升级比特币协议的方式使用更小的单位,比如:1微聪。</p>
<h2>总结</h2>
<p>黄金和比特币总量都是固定的,都不是由政府控制的,从布雷顿森林体系解体之后,黄金相对于美元的价格从35美元/盎司涨到了现在的1330美元/盎司,这是由于美元的通胀造成的。</p>
<pre><code>黄金在历史打败了他的竞争对于:贝壳,铁,铜,银等。但是黄金在近代却输给了纸币,现在信用货币在被大量的使用(银行卡,信用卡,虚拟银行等)。但是纸币有个致命的缺点就是通货膨胀,持续的通货膨胀让美元的"含金量"越来越小。比特币在建立在互联网上面的,先天就适合互联网的特点,其在流通性,鉴别性,细分上面远远的超过了黄金。比特币将会取代黄金,比特币的总市值将会超过黄金的总市值。</code></pre>
<p>目前黄金的总开采量大约14万吨,总市值是6.5万亿美元。比特币开采完之后是2100万个。比特币的总价值如果达到当前黄金的最价值,意味着一个比特币的价格会是30万美元左右将近于180万人民币(不考虑通货膨胀的情况下)。<br>赶紧买个比特币压压惊!</p>
是时候考虑下如何使用Splash页了?
https://segmentfault.com/a/1190000008655975
2017-03-11T16:44:30+08:00
2017-03-11T16:44:30+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>第一次写产品相关的文章,首先从<code>App</code>启动开始吧!大多数<code>App</code>启动会都会使用一个页面:<code>Splash页</code>也叫做开屏页,这篇文章就聊下如何使用<code>Splash页</code>。</p>
<h2>什么是Splash页</h2>
<p><code>Splash页</code>的定义:</p>
<blockquote><p>当应用程序(App)启动的时候,启动是要花费一定的时间的,这个时候往往在程序启动过程中会使用一些过渡页面(或动画),这些被成为启动页。由于启动页在每次打开应用时都会出现,并且往往停留很短的时间,就像闪现的效果一样,所以启动页也常常被成为闪屏或者Splash页。</p></blockquote>
<p>为什么我们需要一个启动页?原因就在于<code>App</code>打开的一刻,系统需要加载应用程序的资源,还有可能加载一些缓存资源,可能还需要进行网络请求,这些处理都需要花费一定的时间。如果这段时间内没有给用户反馈和其他形式的展现,用户会以为程序出了问题,还有可能由于等待时间过长没有得到预期的结果而心情烦躁。而这个启动时间客观存在的,所以就有人提出了不如利用启动页做些事情,比如:</p>
<blockquote><p>传递信息(商业信息、产品的核心作用)、故事或是产品的人文情怀</p></blockquote>
<p>有人就应用启动页还总结了:<a href="https://link.segmentfault.com/?enc=RN7mR79y%2BDMZXXfXpWr3gA%3D%3D.w9ms7yrQPktt7Mh7hHrmkMI6H7Lavx5S21CELNbw9FD%2FIgqJYgU%2FQwGpMyScoi%2B1qDiRz5zS6okCj6MVDrJz9g%3D%3D" rel="nofollow">应用黄金七秒启动页</a>里面有一个关于启动页控制在多少时间的观点:</p>
<blockquote><p>启动页在时间上最好控制在3秒以下,超过3秒用户就会有焦急感,如果启动页又没有加载过程等状态反馈,用户很容易就直接退出应用。</p></blockquote>
<p>所以就出现有些应用会在<code>Splash页</code>上多停留一段时间,让用户更多的注意力在启动页上面,以达到:</p>
<ol>
<li><p>品牌宣传</p></li>
<li><p>情感共鸣</p></li>
<li><p>情景故事演绎</p></li>
</ol>
<p>那当前<code>Splash页</code>的现状又是如何了?</p>
<h2>Splash现状</h2>
<p>微信的经典页面:<br><img src="/img/remote/1460000008869068" alt="微信" title="微信"></p>
<p>微博的:<br><img src="/img/remote/1460000008869069" alt="微博" title="微博"></p>
<p>唯美的:<br><img src="/img/remote/1460000008869070" alt="唯美" title="唯美"></p>
<p>情怀的:<br><img src="/img/remote/1460000008869071" alt="情怀" title="情怀"></p>
<p>可交互的:<br><img src="/img/remote/1460000008869072" alt="多张" title="多张"></p>
<p>每次都启动,且<code>Splash页</code>带动画的:<br><img src="/img/remote/1460000008869073" alt="滴滴" title="滴滴"></p>
<p>每次启动之后,播放完一页之后还会再有一页的:<br><img src="/img/remote/1460000008655984" alt="ofo1" title="ofo1"></p>
<p><img src="/img/remote/1460000008655985" alt="ofo2" title="ofo2"></p>
<h2>应该使用什么样的Splash页</h2>
<p>那应该使用什么样的<code>Splash</code>页?在回答这个问题之前先说两个观点:<br>罗振宇在2017年在他的跨年演讲中说了一个时间战场的观点:</p>
<blockquote><p>未来,在时间这个战场上,有两门生意会特别值钱,第一,就是帮别人省时间。第二,就是帮别人把省下来的时间浪费在那些美好的事物上。</p></blockquote>
<p>张小龙在微信公开课上阐述了"用完即走"的理念:</p>
<blockquote><p>任何产品都只是一个工具,对工具来说,好的工具就是应该最高效率的完成任务,然后尽快离开。</p></blockquote>
<p>他们都表述了一个事实,用户的时间很有限,有限的时间应该花在有用的事情上面,有限的时间应该花在美好的事情上面,浪费用户时间而对于有没有价值的事情在产品设计里面不要去做。比如:<code>Splash页</code>就不应该存在。<br>好的Splash页应该是什么样的呢?<br>这里还有苹果在「<code>iOS Human Interface Guidelines</code>」中关于启动的描述:</p>
<blockquote><p>为了增强应用程序启动时的用户体验,您应该提供一个启动图像。启动图像与应用程序的首屏幕看起来非常相似。当用户在主屏幕上点击您的应用程序图标时,iPhone OS会立即显示这个启动图像。一旦准备就绪,您的应用程序就会显示它的首屏幕,来替换掉这个启动占位图像。一定要强调的是,之所以提供启动图像,是为了改善用户体验,并不是为了提供:</p></blockquote>
<p>「应用程序进入体验」,比如启动动画<br>「关于」窗口<br>品牌宣传元素,除非它们是您应用程序首屏幕的静态组成部分</p>
<p>苹果描述启动的这个章节也叫做<code>立即启动</code>,<code>iOS 程序应该在用户想用它们的时候立刻启动,毫无延迟 </code>,应该尽量避免呈现"关于"或者"splash"页面,苹果的建议是使用一张和应用程序第一屏一样的启动图片,这样可以缩短对启动时间的感受。也就是说让用户感觉<code>App</code>已经启动,其实只是使用了一张图而已,而只要启动时间足够短,当用户真正想做操作的时候,主页面已经加载完成了,让用户是感知不到启动时间。</p>
<blockquote><p>好的<code>Splash页</code>就是没有<code>Splash页</code></p></blockquote>
<p>你需要的是优化应用的启动时间,尽量让主页面的启动控制在200ms左右,这个时间用户很难感知到应用在启动,这样的好处是用户打开你的App就能有直接进入的感觉,得到自己想要的功能,而不是每次打开时都需要欣赏3秒情怀图片。<br>另外:不能只看到微信经典图片的情怀,而不注意到,微信在进程不被杀死的情况下再次打开微信是不会出现<code>Splash页</code>(android back键返回不会杀死进程)。</p>
<p>用户的时间越来越重要,让用户把时间花费在有价值的事情上,减少用户的等待,用户打开App是想要功能(价值),而不是每次都有的:<strong>3秒钟情怀</strong>。</p>
<p>有人说技术那里回复说:<em>启动时间无法缩短怎么办?</em>那表示着你应该找个更靠谱的技术了!</p>
MacOS编译安装Caffe
https://segmentfault.com/a/1190000008532537
2017-03-01T18:56:46+08:00
2017-03-01T18:56:46+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p><code>Caffe</code>是一个清晰而高效的深度学习框架,是纯粹的C++、CUDA架构,支持命令行,Python和MATLAB接口,可以在CPU和GPU直接无缝切换,Caffe的优势:</p>
<ol>
<li><p>上手快,模型和相应优化都是以文本形式而非代码形式给出,<code>Caffe</code>给出了模型的定义,最优化设置以及预训练的权重,方便立即上手。</p></li>
<li><p>速度快,Caffe与cuDNN结合使用,能够运行最棒的模型和海量的数据。</p></li>
<li><p>模块化,方便拓展新的认知和设置.</p></li>
<li><p>开源,开放</p></li>
</ol>
<p><code>Caffe</code>在MacOS可以使用<code>Homebrew</code>安装,也是自行编译源代码安装,这里介绍下直接编译源代码安装.</p>
<h2>下载Caffe源代码</h2>
<p><code>Caffe</code>的github地址:<a href="https://link.segmentfault.com/?enc=UjxQzvQPCETacKOR9ILttg%3D%3D.7NujI1pKiL6ZIcEWaEu9XKpRxtc9aJ6eX9tnK6PVrts%3D" rel="nofollow">https://github.com/BVLC/caffe</a>,直接克隆Caffe源代码:</p>
<blockquote><p>git clone git@github.com:BVLC/caffe.git</p></blockquote>
<p>复制<code>Makefile.config</code>文件:</p>
<blockquote><p>cd caffe<br>cp Makefile.config.example Makefile.config</p></blockquote>
<p><code>Makefile.config</code>文件中有些编译选项需要做更改才能在MacOS上面编译通过,后面会介绍,下面先介绍下编译Caff所需要的依赖。</p>
<h2>安装Caffe依赖</h2>
<p>编译<code>Caffe</code>需要有大量的依赖,开始扁你之前,需要先安装这些依赖。<br>(1) 使用GPU模式需要安装CUDA,安装CUDA的命令:</p>
<blockquote><p>brew cask install cuda</p></blockquote>
<p>也可以不采用GPU模式,只使用CPU,在<code>Makefile.config</code>中做修改:</p>
<blockquote><p>CPUU_ONLY=1</p></blockquote>
<p>(2) Boost<br><code>Caffe</code>使用的是c++开发,如果要使用python调用<code>Caffe</code>的接口的话,需要安装boot.python:</p>
<blockquote><p>brew install boost --with-python<br>brew install boost-python</p></blockquote>
<p>(3) OpenCV 安装,直接安装OpenCV</p>
<blockquote><p>brew install opencv</p></blockquote>
<p>OpenCV安装之后需要在<code>Makefile.config</code>中设置OpenCV的文件头路径,以及lib的路径:</p>
<pre><code>
INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include /usr/local/Cellar/opencv/2.4.13.2/include
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/local/Cellar/opencv/2.4.13.2/lib
</code></pre>
<p>(4) 数据库leveldb,lmdb,hdf5安装:</p>
<blockquote><p>brew install leveldb<br>brew install lmdb<br>brew tap homebrew/science<br>brew install install homebrew/science/hdf5</p></blockquote>
<p>(5) 日志与数据操作</p>
<blockquote><p>brew install protobuf<br>brew install glog<br>brew install gflags<br>brew install snappy</p></blockquote>
<h2>安装caffe-python依赖</h2>
<p>先要安装 Python依赖库:numpy,h5py以及scikit-image</p>
<blockquote><p>brew install numpy<br>pip install h5py<br>pip install scikit-image</p></blockquote>
<p>安装完Python的依赖类库之后需要注意以下的4点:</p>
<ol><li><p>设置<code>Makefile.config</code>中numpy的路径:</p></li></ol>
<blockquote><p>PYTHON_INCLUDE := /usr/include/python2.7 \</p></blockquote>
<ol>
<li><p>在python中使用OpenCV,需要把OpenCV安装目录下<code>../python/site-packages</code>里面的两个文件<code>cv.py</code>和<code>cv2.so</code>拷贝到<code>/usr/local/lib/python2.7/site-packages</code>目录下,这样python才能调用OpenCV。</p></li>
<li><p>在<code>Makefile.config</code>设置<code>WITH_PYTHON_LAYER:=1</code></p></li>
<li><p>在<code>Makefile.config</code>中设置PYTHON_LIB:</p></li>
</ol>
<blockquote><p>PYTHON_LIB := /usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib</p></blockquote>
<h2>编译Caffe</h2>
<p>使用<code>make</code>命令编译Caffe:</p>
<blockquote><p>make clean<br>make all <br>make test<br>make runtest</p></blockquote>
<p>编译Caffe-Python:</p>
<blockquote><p>make pycaffe</p></blockquote>
<p>以上编译都通过之后将caffe/python添加到python系统路径里<code>fish</code>设置命令:</p>
<blockquote><p>set -gx PYTHONPATH path/to/caffe/python $PYTHONPATH</p></blockquote>
<p><code>bash</code>的设置命令:</p>
<blockquote><p>export PYTHONPATH=path/to/caffe/python:$PYTHONPATH</p></blockquote>
<h2>使用Caffe</h2>
<p>在命令行中直接测试<code>Caffe</code>是否编译成功。</p>
<blockquote><p>python<br>import caffe</p></blockquote>
<p>没有错误出现则表示<code>Caffe</code>安装成功。</p>
Linux中的文件复制:cp和scp
https://segmentfault.com/a/1190000008487051
2017-02-26T09:55:13+08:00
2017-02-26T09:55:13+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在使用操作系统的使用过程中,常常需要复制文件到本地或者传输文件到其他电脑上,这时候用到两个命令<code>cp</code>和<code>scp</code>。<br><code>cp</code>命令用来复制文件或者目录。<code>scp</code>是secure copy的简写,用来在Linux下进行加密的远程传输文件或者目录。<br><code>cp</code>和<code>scp</code>是Linux中功能强大且常用的的命令,下面就介绍下cp和scp两个命令的使用。</p>
<h2>cp命令</h2>
<p>cp命令可以复制一个文件,可以是单个文件复制也可以是整个目录复制,命令的使用方式:</p>
<blockquote><p>cp [options] source dest</p></blockquote>
<p>例如:<code>cp test.txt test1.txt</code>,就是把test.txt复制为文件test1.txt。<br>常用的cp命令选项有:</p>
<pre><code>
`-r`:拷贝目录文件,将目录下的文件依序拷贝到目标目录中
`-f`:如果目标文件中已经有相同的文件名存在,在复制前先将同名文件删除再进行复制
`-l`:对源文件进行硬链接,而不复制文件
`-u`:源文件的Modification Time较目的文件有更新的时候,才会进行复制。
`-v`:输出详细信息
</code></pre>
<p>复制文件夹的命令:</p>
<blockquote><p>cp -r test/ test1/</p></blockquote>
<h2>scp命令</h2>
<p><code>scp</code>命令可以实现本地与远程服务器之间的双向传输,本地文件可以传输到远程服务,也可以把远程服务器上的文件传输到本地,而且是加密的。<br><code>scp</code>命令的基本格式:</p>
<blockquote><p>scp [options] source dest</p></blockquote>
<p>scp常用的命令选项:</p>
<pre><code>
`-P`:数据传输默认端口,默认是22
`-r`:递归拷贝整个目录
`-i`:指定密钥文件,参数直接传递给ssh使用
`-l`:限定网速,以Kbit/s为单位
`-C`:允许压缩
`-1,-2`:强制scp命令使用ssh1或者ssh2协议
`-4,-6`:使用ipv4或者ipv6寻址</code></pre>
<p>下面是比较常用的<code>scp</code>命令使用的例子。</p>
<ol>
<li>
<p>本地文件传输到远程服务器</p>
<pre><code>命令格式:
>scp test.txt root@192.168.1.1:/home/
将test.txt文件复制到目标服务器(192.168.1.1)下的home文件夹下。</code></pre>
</li>
<li>
<p>本地文件夹传输到远程服务器</p>
<pre><code>命令格式:
>scp -r test root@192.168.1.1:/home/
将test整个文件夹复制到目标服务器下的home文件夹下。</code></pre>
</li>
<li>
<p>远程服务器文件传输到本地</p>
<pre><code>命令格式:
>scp root@192.168.1.1:/home/test.txt test
将远程服务中home目录下的`test.txt`文件,复制到本地的test目录下
</code></pre>
</li>
<li>
<p>远程服务器文件夹复制到本地</p>
<pre><code>>scp -r root@192.168.1.1:/home/test /Users/jjz
将远程服务器中home目录下的test整个目录复制到本地的jjz目录下</code></pre>
</li>
<li>
<p>scp命令指定密钥文件</p>
<pre><code>>scp test.txt root@192.168.1.1:/home/ -i ~/.ssh/id_rsa.1
这里指定了密钥文件`id_rsa.1`做为ssh的连接参数,不使用默认的密钥文件。</code></pre>
</li>
</ol>
Ubuntu中的用户管理
https://segmentfault.com/a/1190000008483199
2017-02-25T18:23:03+08:00
2017-02-25T18:23:03+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在使用Linux系统的时候,要坚持<code>最小权限原则</code>.<code>最小权限原则(least priviledge)</code>是指Linux通常希望用户或者进程只拥有足够完成其工作的权限,而系统不赋予其更多的特权。<br>最高权限的用户通常是<code>root</code>用户,<code>root</code>用户想做什么都可以(代表着最大权限)。如果都使用每个进程都用<code>root</code>权限,这对于系统来说是一个巨大的安全漏洞,因此不能使用<code>root</code>用户部署服务,降低部署进程的权限。使用一个特定用户用来做部署特定的服务是一种比较常用的方式。对于该用户要收缩其所享有的特权,以防权限的滥用。因此在Ubuntu中需要管理好相关用户。</p>
<h2>新建用户</h2>
<p>使用命令<code>useradd</code>可以新建一个用户:</p>
<blockquote><p>sudo useradd jjz</p></blockquote>
<p>adduser会自动创建<code>用户目录</code>和<code>shell</code>,并且自动创建<code>分组</code>。<br>使用passwd命令可以为用户设置密码:</p>
<blockquote><p>sudo passwd jjz</p></blockquote>
<pre><code>sudo: unable to resolve host
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
</code></pre>
<p>这样就可以给用户<strong>jjz</strong>设置一个密码了。<br>使用<code>id</code>命令可以查看用户的信息:</p>
<blockquote>
<p>sudo id jjz</p>
<pre><code>uid=1000(jjz) gid=100(users) groups=100(users)
</code></pre>
</blockquote>
<p>使用finger命令可以查看用户的目录,shell和输入的地址等详细信息:</p>
<blockquote><p>sudo finger jjz</p></blockquote>
<pre><code>Login: jjz Name:
Directory: /home/jjz Shell: /bin/sh
Never logged in.
No mail.
No Plan.</code></pre>
<h2>修改和删除用户</h2>
<p>命令usermod用来修改用户信息,修改用户的登录名:</p>
<blockquote><p>usermod -l jjz jjz1</p></blockquote>
<p>也可以用来把用户加入到分组中例如:</p>
<blockquote><p>usermod -g users jjz</p></blockquote>
<p>还可以用来修改用户的用户目录:</p>
<blockquote><p>usermod -d /user/jjz jjz</p></blockquote>
<p>如果想删除用户可以使用命名userdel:</p>
<blockquote><p>userdel jjz</p></blockquote>
<p>删除用户的时候,同时删除用户的工作目录可以使用命令:</p>
<blockquote><p>userdel -r jjz</p></blockquote>
<h2>用户与root用户的切换</h2>
<p>使用非root用户登录之后,有时需要执行一些具有root权限的操作,比如安装系统级别的软件,修改系统文件等,经常需要用sudo权限,这个时候我们也可以切换到root用户进行操作,切换<code>root</code>用户可以使用命令:</p>
<blockquote><p>sudo su</p></blockquote>
<p>切换到root用户需要验证当前用户密码。<br>使用完root用户之后,可以使用命令切换到登录其他用户:</p>
<blockquote><p>su jjz</p></blockquote>
<p>或者直接使用<code>exit</code>即可退出root用户,回到登录用户。</p>
<h2>给用户赋予执行sudo权限</h2>
<p>当新用户执行<code>sudo</code>的时候会提示<code>xxx is not in the sudoers file. This incident will be reported.</code>,也就是说当前用户是没有执行sudo权限的。如果我们想让该用户拥有执行sudo的权限,需要给用户授权,<code>sudo</code>权限的授权需要修改文件<code>/etc/sudoers</code>。<br>首先进入root用户模式:</p>
<blockquote><p>su</p></blockquote>
<p>添加文件的写权限:</p>
<blockquote><p>chmod u+w /etc/sudoers</p></blockquote>
<p>编辑<code>/etc/sudoers</code>:</p>
<blockquote><p>vim /etc/sudoers</p></blockquote>
<p>找到<code>root ALL=(ALL)ALL</code><br>在它的下面添加:</p>
<pre><code>
jjz ALL=(ALL)ALL
</code></pre>
<p>这样<code>jjz</code>即可执行<code>sudo</code>。<br>如果要撤销文件的写权限可以使用命令:</p>
<blockquote><p>chmod u-w /etc/sudoers</p></blockquote>
<h2>禁用和启用root用户</h2>
<p>为了系统的充分执行最小授权原则,我们也可以禁止root用户登录,禁用root用户的命令:</p>
<blockquote><p>sudo passwd -l root</p></blockquote>
<p>这样就禁用了root用户登录,但是root的密码还保存着。<br>再次启动root登录,执行命令:</p>
<blockquote><p>sudo passwd -u root</p></blockquote>
使用ssh免密码登录Linux服务器
https://segmentfault.com/a/1190000008479071
2017-02-25T09:38:28+08:00
2017-02-25T09:38:28+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>频繁登录Linux服务器时,使用<code>ssh <username>@<host></code>的方式登录,但是每次都需要输入密码是件很麻烦的事。我们还可以使用私钥/公钥对的方式在免密码登录服务器。<br>首先需要在远程服务器中安装<code>ssh-server</code>服务,才可以使用ssh登录。如果没有的话可以使用命令直接安装<code>ssh-server</code>,可以以Ubuntu为例安转<code>ssh-server</code>:</p>
<blockquote><p>sudo apt-get install openssh-server</p></blockquote>
<h2>生成私钥/公钥对</h2>
<p>使用命令<code>ssh-kengen</code>可以生成私钥/公钥对,私钥/公钥对的生成算法有两种RSA和DSA。</p>
<blockquote><p>RSA是非对称加密算法,可以用来加密和签名<br>DSA(Digital Signature Algorithm)只可以用来数字签名的算法</p></blockquote>
<p>这里使用<em>RSA</em>算法生成私钥/公钥对。需要确认<code>.ssh</code>目录是否存在,如果不存在的话创建该目录:</p>
<blockquote><p>mkdir ~/.ssh<br>chmod 700 ~/.ssh</p></blockquote>
<p>然后生成私钥/公钥对:</p>
<blockquote><p>ssh-keygen -b 1024 -t rsa</p></blockquote>
<pre><code> Generating public/private rsa key pair.
Enter file in which to save the key (/Users/user/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/user/.ssh/id_rsa.
Your public key has been saved in /Users/user/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:gh0yqSZhpP9ERlhFSKwgy3sTBZwPAT0InLBJ73zcNt8 user@user-ios.local
The key's randomart image is:
+---[RSA 1024]----+
|*+*BB+o |
|**oBoo |
|B+ o@ . |
|o++=.*.. |
|. =o+oo+S |
| + =. ..o . |
| . o . E |
| |
| |
+----[SHA256]-----+</code></pre>
<p>其中<code>-b</code>的参数是用来设置私钥的长度1024的长度已经可以满足我们对于安全的需求了,不输入任何文件名会在<code>.ssh</code>目录下得到两个文件:<code>id_rsa</code>和<code>id_rsa.pub</code>。</p>
<h2>上传公钥到对应的服务器</h2>
<p>使用命令<code>ssh-copy-id</code>可以将认证文件加载到对应的服务器上:</p>
<blockquote>
<p>ssh-copy-id -i ~/.ssh/id_rsa.pub <username>@<host></p>
<pre><code> /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
user@host's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'user@host'"
and check to make sure that only the key(s) you wanted were added.
</code></pre>
</blockquote>
<p>这里会要求我们输入远程服务器的密码。</p>
<h2>修改服务器的ssh配置文件</h2>
<p><code>ssh-server</code>配置文件位于:<code>/etc/ssh/sshd_config</code>中,需要设置<code>ssh-server</code>允许使用私钥/公钥对的方式登录,打开配置文件:</p>
<blockquote><p>vim /etc/ssh/sshd_config</p></blockquote>
<p>增加设置:</p>
<pre><code>RSAAuthentication yes
PubkeyAuthentication yes
#AuthorizedKeysFile %h/.ssh/authorized_keys
</code></pre>
<p>然后重启ssh-server:</p>
<blockquote><p>sudo /etc/init.d/ssh restart</p></blockquote>
<p>设置完成之后就可以使用命令<code>ssh <username>@<host></code>直接登录服务器了,不需要再输入密码了。</p>
<h2>其他</h2>
<p>1.如果公钥丢失的情况,可以使用私钥再次生成公钥,使用私钥生成公钥的命令</p>
<blockquote><p>ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub</p></blockquote>
<p>2.使用<code>ssh-copy-id</code>上传公钥到服务器之后,公钥是存放在服务器的<code>~/.ssh/authorized_keys</code>中。它的存在格式是一行一个公钥,也可以手动把公钥的内容直接复制到服务器的authorized_keys中。使用命令<code>cat id_rsa.pub</code>可以获取到公钥的内容。</p>
MacOS安装OpenCV 3
https://segmentfault.com/a/1190000007761481
2016-12-11T09:24:30+08:00
2016-12-11T09:24:30+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>OpenCV(Open Source Computer Vision Library)是一个基于开源发行的跨平台计算机视觉库,而OpenCV 3是2014发布的OpenCV 2的升级版本,它带来了一些提升:</p>
<ul>
<li><p>大体上保留了OpenCV 2的经典c++和python编程接口的风格</p></li>
<li><p>架构调整</p></li>
<li><p>更多新的算法</p></li>
<li><p>引入T-API,使OpenCL加速更容易</p></li>
<li><p>更多指令优化集</p></li>
</ul>
<p>在MacOS Sierra上安装了下OpenCV 3。</p>
<h2>前期准备</h2>
<p>需要先安装cmake:</p>
<blockquote><p>brew install cmake</p></blockquote>
<p>源代码下载,在OpenCV的官网上面下载代码,下载地址:<a href="https://link.segmentfault.com/?enc=wxSvrWAf%2Bu7L97sYRcDEXw%3D%3D.%2F3iF5AmkF9lTfkgYvDIyEqt7bf5OfiMtoW1F%2F0x23yI%3D" rel="nofollow">http://opencv.org/</a>,也可以在github上面下载源代码:<a href="https://link.segmentfault.com/?enc=mgk0dz0z5bSM4sghKOcx6Q%3D%3D.Pq%2B1nDnlrQf5RPFVPfG%2BzUW1H4wJHlvH1nC%2FWhVMIxO17X2ubkYKZL1hus8UY8Zp" rel="nofollow">https://github.com/opencv/opencv</a>,在github下载的源代码需要切换到3.1.0的tag:</p>
<blockquote><p>git tag 3.1.0</p></blockquote>
<p>也可以选择使用Homebrew安装,这样比较方便的解决安装依赖的问题,这里以使用Homebrew安装为例。</p>
<h2>安装</h2>
<p>安装命令:</p>
<blockquote><p>brew opencv3</p></blockquote>
<p>这时候遇到了第一个问题:</p>
<pre><code> XXX/cap_qtkit.mm:46:9: fatal error:
'QTKit/QTKit.h' file not found
#import
^
1 error generated.
make[2]: *** [modulesideoio/CMakeFiles/opencv_videoio.dir/src/cap_qtkit.mm.o] Error 1
make[1]: *** [modulesideoio/CMakeFiles/opencv_videoio.dir/all] Error 2
make: *** [all] Error 2</code></pre>
<p>原因是MacOS Sierra中将原有的<code>QTKit.framework</code>进行了更改,移除了部分文件,将QTKit升级为了AVKit,这个问题在<a href="https://link.segmentfault.com/?enc=Y7gqk%2FWd%2B5gxIy83FTeHng%3D%3D.usF8b5voeREw%2BEQVwebOzlYR61K5JmrHzKRrN9OCY4kedbELdpKRGPH6tKT4fHO4" rel="nofollow">PR</a>中已经解决了,但是还没有发布正式的版本,因此需要在安装中指定使用--HEAD.</p>
<blockquote><p>brew -v install --HEAD opencv3</p></blockquote>
<p>安装过程有可能会出现的另一个错误:</p>
<pre><code>/tmp/opencv3-20161203-50490-128l1si/opencv-3.1.0/modules/videoio/src/cap_qt.cpp:63:10: fatal error: 'QuickTime/QuickTime.h' file not found
#include <QuickTime/QuickTime.h>
^
1 error generated.
make[2]: *** [modules/videoio/CMakeFiles/opencv_videoio.dir/src/cap_qt.cpp.o] Error 1
make[1]: *** [modules/videoio/CMakeFiles/opencv_videoio.dir/all] Error 2
make: *** [all] Error 2
READ THIS: https://git.io/brew-troubleshooting</code></pre>
<p>可以尝试下安装命令:</p>
<blockquote><p>brew -v install --with-contrib --with-ffmpeg --with-gphoto2 --with-gstreamer --with-java --with-libdc1394 --with-opengl --with-openni2 --with-python3 --with-qt5 --with-tbb --with-vtk --HEAD opencv3</p></blockquote>
<p>OpenCV可以单独使用,也可以和<code>CUDA</code>混合使用。</p>
<h2>CUDA</h2>
<p>CUDA(Compute Unified Device Architecture)是<code>NVIDIA</code>推出的运算平台,是一种并行计算架构,该架构使GPU能够解决复杂的计算问题,如果我们想使用CUDA和OpenCV混合编程,需要使用CUDA重新编译OpenCV:</p>
<blockquote><p>brew -v --with-cuda install --HEAD opencv3</p></blockquote>
<p>如果没有安装CUDA的会得到提示:</p>
<pre><code>opencv3: CUDA is required.To use this formula with NVIDIA graphics cards you will need to
download and install the CUDA drivers and tools from nvidia.com.
https://developer.nvidia.com/cuda-downloads
Select "Mac OS" as the Operating System and then select the
'Developer Drivers for MacOS' package.
You will also need to download and install the 'CUDA Toolkit' package.
The `nvcc` has to be in your PATH then (which is normally the case).
CudaRequirement unsatisfied!
You can install with Homebrew-Cask:
brew cask install cuda
</code></pre>
<p>使用命令<code>brew cask install cuda</code>按照提示可以完成CUDA的安装。</p>
<p>另外在这里记录下CUDA在Ubuntu上安装的方法。可以在官网中看到Linux上的安装方式,进入<a href="https://link.segmentfault.com/?enc=rz6h%2B8QfCProHdP0tD6puw%3D%3D.59oMQXfe%2BmGVrmlcXvmBiPGyvnSdKR3ZbobiZfGvgtt1M63gHOxU7iOvqKxH1WSk" rel="nofollow">https://developer.nvidia.com/cuda-downloads</a>,选择<code>Linux</code>,再选择<code>平台</code>,<code>Linux发行版</code>,以及<code>版本</code>就可以看到下载安装的方式,有两种安装方式:<code>网络安装</code>和<code>本地安装</code>:</p>
<ul><li><p>网络安装,先下载deb包:</p></li></ul>
<pre><code>wget http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_8.0.44-1_amd64.deb</code></pre>
<p>运行安装命令:</p>
<pre><code>sudo dpkg -i cuda-repo-ubuntu1604_8.0.44-1_amd64.deb
sudo apt-get update
sudo apt-get install cuda</code></pre>
<ul><li><p>本地安装,需要下载完整的deb安装包:</p></li></ul>
<pre><code>wget http://developer.download.nvidia.com/compute/cuda/8.0/secure/prod/local_installers/cuda-repo-ubuntu1604-8-0-local_8.0.44-1_amd64.deb?autho=1481416577_ed84fa072b0f3fb5f814762777672f7e&file=cuda-repo-ubuntu1604-8-0-local_8.0.44-1_amd64.deb</code></pre>
<p>运行命令安装:</p>
<pre><code>sudo dpkg -i cuda-repo-ubuntu1604-8-0-local_8.0.44-1_amd64.deb
sudo apt-get update
sudo apt-get install cuda</code></pre>
OpenCV Python安装教程
https://segmentfault.com/a/1190000007758856
2016-12-10T18:33:40+08:00
2016-12-10T18:33:40+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p><a href="https://link.segmentfault.com/?enc=besr7%2FguKcPEMPBDHBok7Q%3D%3D.p4GHFLV95mkYW3qhadcSrYXoZaJk7tQupbbpdJTRip4%3D" rel="nofollow">OpenCV</a>全称:Open Source Computer Vision Library,是一个开源的跨平台计算机视觉库,github地址:<a href="https://link.segmentfault.com/?enc=D5zhzH7HR71S5%2F5vr2i%2Fnw%3D%3D.IZ1uDCsJnmcpr9r1a5ZQP%2BF4rFmDBKt5%2BqotboH0nKQ7HHzFUeMoJzKFfSUkjSdz" rel="nofollow">https://github.com/opencv/opencv</a>。OpecCV主要用c++语言编写,也提供了python等其他语言的,下面介绍下如何在MacOS和Ubuntu上如何安装OpenCV以及OpenCV的Python调用库。</p>
<h2>MacOS上面安装</h2>
<h4>安装OpenCV</h4>
<p>使用Homebrew安装直接安装OpenCV:</p>
<blockquote><p>brew install opencv</p></blockquote>
<p>OpenCV的安装目录为:<code>/usr/local/Cellar/opencv/</code>。<br>也可以从<a href="https://link.segmentfault.com/?enc=UUJS5hT1lwaG%2Beyc6ZIbvg%3D%3D.dmggKNOd1GwDwoPogeBT%2Fp3UclWvmy5ZKx3Bq%2Bi6wBU%3D" rel="nofollow">官网</a>下载安装包直接安装。比较复杂的是下载源代码使用<code>cmake</code>安装。</p>
<h3>cv2</h3>
<p>OpenCV在Python中调用的时候使用的库是<code>cv2</code>。在python中可以直接使用<code>cv2</code>:</p>
<blockquote><p>import cv2</p></blockquote>
<p>直接<code>import</code>会出现错误:</p>
<pre><code>Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named cv2</code></pre>
<p>这是因为Python并不能找到对应的OpenCV的库。<br>那<code>cv2</code>在那里呢?使用<code>pip install</code>安装,也没有找到对应的cv2库。回头看OpenCV的安装目录在<code>lib</code>下面可以看到一个<code>python2.7</code>的包,Python的相关引用都在里面,在<code>site-packages</code>目录下有两个文件:</p>
<pre><code>cv.py
cv2.so</code></pre>
<p>可以把这两个文件复制Python库目录<code>/usr/local/lib/python2.7/site-packages</code>下面,这样在调用的时候Python就可以找到<code>cv2</code>的库。</p>
<h2>Ubuntu上安装OpenCV的方法</h2>
<p>先Ubuntu上可以直接编译OpenCV安装,首先要安装编译需要的依赖包:</p>
<blockquote><p>sudo apt-get install cmake build-essential libgtk2.0-dev libjpeg8-dev libjpeg-dev libavcodec-dev libavformat-dev libtiff5-dev cmake libswscale-dev</p></blockquote>
<p>下载OpenCV的源代码:</p>
<blockquote><p>wget <a href="https://link.segmentfault.com/?enc=sm%2B87C3M2HXHHmkB772gZw%3D%3D.H4XpTAjheq%2FhlMCbZ38gpMIjzm3G%2Fd3%2Frk7Q0HFjSE2c2bl8miw4BDCORTYbCpKfYhelJ1XqSC0VhGDnNJj9gg%3D%3D" rel="nofollow">https://codeload.github.com/o...</a></p></blockquote>
<p>解压:</p>
<blockquote><p>tar -xzvf 2.4.13.tar.gz</p></blockquote>
<p>编译并安装:</p>
<blockquote><p>cd opencv-2.4.13</p></blockquote>
<p>cmake<br>make<br>sudo make install</p>
<p>另外在Ubuntu上使用Python调用OpenCV需要安装对应的python包:</p>
<blockquote><p>sudo apt-get install python-opencv</p></blockquote>
<p>完整脚本地址:<a href="https://link.segmentfault.com/?enc=YtcEtaogzAk%2FXfHB214qRg%3D%3D.T4EscD%2B5j6PzpW2sCAav14tfX71ITgCPcYJGr71tIC4bds4U454dhdgfA9n%2B%2F98cn9mA8sODQb6Ua5YJcaTmuw%3D%3D" rel="nofollow">https://github.com/jjz/script/blob/master/opencv-python.sh</a></p>
<h3>使用OpenCV</h3>
<p>一个简单用来验证Python是否能够调用OpenCV的方法:</p>
<blockquote><p>import cv2<br>cv2.__version__</p></blockquote>
<p>可以得到OpenCV版本:</p>
<pre><code>'2.4.13.1'</code></pre>
Beego自动化文档(最新版)
https://segmentfault.com/a/1190000007613046
2016-11-27T11:24:59+08:00
2016-11-27T11:24:59+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>之前写过一篇使用Beego自动化api文档的文章:<a href="https://link.segmentfault.com/?enc=pG%2FDv64AUw9ZOk7HXZgb8A%3D%3D.jeqoWHsdiEjVEHmctmR1MPJLGOsCwaKzc0XJK7dz8MKFr4mbvRMKgHiVobqzUo5a" rel="nofollow">Beego自动化文档</a>,随着Beego的更新,1.7.0之后Beego自动化文档的方法也有了更新,最显著的更新是去掉了<code>docs.go</code>,使用了<code>swagger.json</code>,更加的符合swagger的特点。这篇文章是上一篇文章的修正和补充。</p>
<h2>环境要求</h2>
<p>需要安装最新的Go语言环境,安装Go可以参考<a href="https://link.segmentfault.com/?enc=AVTUb%2F18Ljk54ZJW5coLSA%3D%3D.n1o%2BBv67CgwduWWLV6KNJQgCzyk5Trw7%2BzDI5rSEGOOm50RsqsQ6%2F%2B8FMMxpQviM" rel="nofollow">Golang在Mac OS上的环境配置</a>,还需要安装最新的Beego框架。如果是你的Beego框架还是旧版本的就需要升级Beego:</p>
<blockquote><p>go get -u github.com/astaxie/beego<br>go get -u github.com/beego/bee</p></blockquote>
<p>查看bee的最新版本:</p>
<blockquote><p>bee version</p></blockquote>
<pre><code>| ___ \
| |_/ / ___ ___
| ___ \ / _ \ / _ \
| |_/ /| __/| __/
\____/ \___| \___| v1.5.2
├── Beego : 1.7.1
├── GoVersion : go1.7.3
├── GOOS : darwin
├── GOARCH : amd64
├── NumCPU : 8
├── GOPATH : /Users/jjz/Documents/go
├── GOROOT : /usr/local/Cellar/go/1.7.3/libexec
├── Compiler : gc
└── Date : Saturday, 26 Nov 2016</code></pre>
<p>可以从该命令中看到Go的环境配置,Beego的版本等信息。</p>
<h2>文档的生成</h2>
<p>在<code>conf/app.conf</code>中设置<code>EnableDocs=true</code>之后,仍然可以通过命令生成文档:</p>
<blockquote><p>bee generate docs</p></blockquote>
<p>只是这里生成的不再是<code>docs.go</code>,而是符合<code>swagger</code>使用的两个文档:</p>
<pre><code>swagger.json
swagger.yml</code></pre>
<p><code>swagger.yml</code>是swagger的契约文档,根据这份文档,可以描述出api的定义规则。而<code>swagger.json</code>描述的是一份符合swagger规则的api数据,通过这个json数据可以在<code>swagger-ui</code>中列出api文档。<br>使用<code>bee generate docs</code>命令可以生成对于对于的两个swagger文件,但是bee项目运行的时候是通过监控文件,自动重新编译项目的,自动重新编译项目也能自动生成文档是最好的,因此Beego在运行项目的时候添加了自动生成文档的命令:</p>
<blockquote><p>bee run -gendoc=true</p></blockquote>
<p>这样可以在每次项目重新运行的时候更新api文档,不用重新运行命令:<code>bee generate docs</code>。</p>
<h2>API文档的访问</h2>
<p>更新<code>swagger-ui</code>是一件很麻烦的事情,所以在beego的运行命令中加入一个参数<code>-downdoc=true</code>:</p>
<blockquote><p>bee run -downdoc=true</p></blockquote>
<p>该命令会监测<code>swagger</code>目录下面是否有<code>swagger-ui</code>的文件,如果没有就从github上面下载,下载的地址是: <a href="https://link.segmentfault.com/?enc=rbZ75wgE2EBY%2FTAbACNBog%3D%3D.am4JWmHlAp8BSbxuw3FWfVxd5Pb7WuL2XtpSVRm5aquyLTbx68XTayo0g%2FpzAtUu" rel="nofollow">https://github.com/beego/swagger/archive/v2.zip</a><br>另外设置静态文件夹的方法也有了变化,新的函数为:</p>
<blockquote><p>beego.SetStaticPath("/swagger", "swagger")</p></blockquote>
<p>设置完静态文件夹之后可以通过url:<a href="https://link.segmentfault.com/?enc=C4bG039zgog%2FZMzzY4h9kQ%3D%3D.kNu%2BaKdPOkJ4%2Fd5nE0M%2BR4GCoQTAzs%2FOJorHu6Q9y%2BKKsKybhqWqlIZXC%2FAvTAKS" rel="nofollow">http://127.0.0.1:8080/swagger/index.html</a>访问swagger文档。打开该文档会自动的请求<code>swagger.json</code>,发现请求的<code>swagger.json</code>文档地址为:<a href="https://link.segmentfault.com/?enc=koxfxFr8KSl4hV%2FvBYSI0A%3D%3D.0nsZ9lP7S2JPkKz3Oo4Oo0m%2FzgyR%2Foi7xYISK8ilrYFlUq9zO8TTNr53PvI9OIRli9uNhuBFfIk%2Bt91N0VLUWQ%3D%3D" rel="nofollow">http://127.0.0.1:8080/swagger/index.html/swagger.json</a>,需要把<code>swagger/index.html</code>中的swagger.json的地址设置为:</p>
<blockquote><p>url = "/swagger/swagger.json";</p></blockquote>
<p>再次访问swagger文档地址就可以看到API文档了:</p>
<p><img src="/img/remote/1460000007613049" alt="swagger文档" title="swagger文档"></p>
<h2>路由解析与注解</h2>
<p>路由解析仍然使用的是<code>namespace+Include</code>:</p>
<pre><code>ns := beego.NewNamespace("/v1",
beego.NSNamespace("/object",
beego.NSInclude(
&controllers.ObjectController{},
),
),
beego.NSNamespace("/user",
beego.NSInclude(
&controllers.UserController{},
),
),
)
beego.AddNamespace(ns)</code></pre>
<p>而注解也是仍然采用以前的注解方式:</p>
<pre><code>// @Title createUser
// @Description create users
// @Param body body models.User true "body for user content"
// @Success 200 {int} models.User.Id
// @Failure 403 body is empty
// @router / [post]
func (u *UserController) Post() {
var user models.User
json.Unmarshal(u.Ctx.Input.RequestBody, &user)
uid := models.AddUser(user)
u.Data["json"] = map[string]string{"uid": uid}
u.ServeJSON()
}</code></pre>
<h2>总结</h2>
<p>新版本的beego中对于swagger的支持更加的友好,也更加的swagger化了,采用了<code>swagger.json</code>和<code>swagger.yml</code>文件,不需要重新生成一个新的router文件了,这样文档部分的代码更加的分离,使用<code>swagger.yml</code>可以更方便的生成访问api的其他语言的代码。</p>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=d%2FXW7jE92W2Vm3ioIrtt5Q%3D%3D.rZKRQNohdQVJrVoQBflfJyNBj1DUfrPXUaYNmaQpyiCkrp13S%2B9qYp4rRYHpsK6p" rel="nofollow">https://github.com/jjz/go/tree/master/autodoc</a></p>
在Beego中使用Jade模板
https://segmentfault.com/a/1190000007532274
2016-11-19T12:26:02+08:00
2016-11-19T12:26:02+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p><a href="https://link.segmentfault.com/?enc=P4ZgqdhoWhlbqomavlMzLg%3D%3D.%2F%2FDiQFhOL6EPXC7IubKAiy%2Fy2Go4597pviHq0GmzgT%2FSBr3t34WZTfG9LrkndPse" rel="nofollow">Jade</a>是一个高性能的HTML模板引擎,它受到<a href="https://link.segmentfault.com/?enc=0Bqwz%2B6Jsy%2BGadF0M6SOcA%3D%3D.F3VTcUW6EY1TkSuTjYj%2Bg9vB47v5aRm%2FPtH%2Fg7bDs1k%3D" rel="nofollow">Haml</a>的影响,是使用JavaScript实现的。Jade在客户端也有支持,它的代码比html可读性要高很多,Jade是一个比较常用的HTML模板。<br><a href="https://link.segmentfault.com/?enc=uK%2Ffzdf%2BZxWoH89yx9qOtA%3D%3D.ZYQZ0CGVniycMs0pLNPaYR6mQ%2Bw16kss3xvF%2Ff9Fa%2Fs%3D" rel="nofollow">Beego</a>是一个go语言的web应用程序开源web框架,而Beego从1.7.0开始支持更加复杂的模板引擎,当然也包括了对于jade的支持,支持更复杂模板引擎的PR地址<a href="https://link.segmentfault.com/?enc=duK9DglhILVqoy8x%2B4ouNg%3D%3D.RqWMGnVRsAEbbMHIdSf5q5iQIchrUD4tIy%2FdVlJ4OarFdQ2GJCIjLJagvClc%2Fo%2BM" rel="nofollow">https://github.com/astaxie/beego/pull/1940</a>。<br>在介绍Jade的使用之前先来看下Go下面的<code>html/template</code>包。</p>
<h2>html/template</h2>
<p>在Go语言中,<code>html/template</code>包是一个很强大的html模板包,结合<code>text/template</code>的模板语法,基本上可以满足大部分的HTML模板需求,无论是Beego中是默认支持的两种模板格式<code>.tpl</code>和<code>.html</code>,以及<code>jade</code>和<code>ace</code>都是可以解析为<code>html/template</code>中的Template对象使用,就是说我们所使用的html模板最终都是基于<code>html/template</code>包实现的。<br><code>html/template</code>使用实例:</p>
<pre><code>package main
import (
"html/template"
)
type User struct {
Name string
}
func main() {
t := template.New("template example")
t, _ = t.Parse("hello {{.Name}}!")
p := User{Name: "jjz"}
t.Execute(os.Stdout, p)
}
</code></pre>
<p>上面的例子会输出字符串:<code>hello jjz</code>。<br>通过上面的例子我们可以看到,如何新建一个模板,再使用模板函数<code>Parse()</code>从字符串中加载模板内容,使用模板函数<code>Execute()</code>可以给模板替换字段。替换模板字段的语法,<code>{{}}</code>中的字段就是要替换字段,<code>{{. Name}}</code>表示需要替换的字段名称。<br><code>Beego</code>使用复杂模板的方式很简单,增加一个模板引擎函数,在项目运行的时候遍历<code>views</code>中的文件,把指定的文件解析为<code>template.Template</code>对象,返回该对象提供使用,这个模板引擎函数就是:`beego.AddTemplateEngine<br>`。</p>
<h2>AddTemplateEngine</h2>
<p><code>beego.AddTemplateEngine</code>是一个用来把指定的文件,转换为<code>template.Template</code>的对象的函数。它的第一个参数是文件的后缀名,在<code>views</code>中的含有此后缀名的文件都会进入该方法进行处理。他的第二个参数是一个函数,用来处理文件的转换,最后会返回一个<code>template.Template</code>的对象。有了这个函数,我们还少一个把jade文件解析为<code>template.Template</code>的方法,还好有人已经做了jade的Go语言实现。</p>
<h2>jade.go</h2>
<p><a href="https://link.segmentfault.com/?enc=yLEb9jFFKbuffyirDScexQ%3D%3D.xRR7j8aA8bikmVHLQQnMzYOpGhh0R%2FbyjxO8o0fGf8o%3D" rel="nofollow">jade.go</a>是Jade的Go语言实现。<br><code>jade.go</code>的使用,首先安装jade.go:</p>
<blockquote><p>go get github.com/Joker/jade</p></blockquote>
<p><code>jade.go</code>使用示例:</p>
<pre><code>func main() {
tpl, err := jade.Parse("name_of_tpl", "doctype 5: html: body: p Hello world!")
if err != nil {
return
}
fmt.Printf( "%s", tpl )
}</code></pre>
<p>输出字符串:</p>
<pre><code><!DOCTYPE html>
<html>
<body>
<p>Hello world!</p>
</body>
</html></code></pre>
<p>有了<code>jade.go</code>就可以在Beego中使用,把jade文件转换为Template对象,在beego中添加<code>jade.go</code>解析jade模板的方法:</p>
<pre><code>func addJadeTemplate() {
beego.AddTemplateEngine("jade", func(root, path string, funcs template.FuncMap) (*template.Template, error) {
jadePath := filepath.Join(root, path)
content, err := utils.ReadFile(jadePath)
fmt.Println(content)
if err != nil {
return nil, fmt.Errorf("error loading jade template: %v", err)
}
tpl, err := jade.Parse("name_of_tpl", content)
if err != nil {
return nil, fmt.Errorf("error loading jade template: %v", err)
}
fmt.Println("html:\n%s",tpl)
tmp := template.New("Person template")
tmp, err = tmp.Parse(tpl)
if err != nil {
return nil, fmt.Errorf("error loading jade template: %v", err)
}
fmt.Println(tmp)
return tmp, err
})
}</code></pre>
<p><code>jade.go</code>目前只支持使用字符串的方式接卸jade模板,因此需要先把.jade文件的内容以字符串的方式读取出来。读取文件的方法:</p>
<pre><code>func ReadFile(path string) (str string, err error) {
fi, err := os.Open(path)
defer fi.Close()
fd, err := ioutil.ReadAll(fi)
str = string(fd)
return
}</code></pre>
<p>jade.go解析出的结果也是一个字符串,因此在代码中需要把字符串转换为<code>template.Template</code>对象,使用了<code> Template.Parse()</code>方法。<br>解析jade模板的引擎方法需要在<code>main()</code>中调用,添加完jade的模板转换引擎之后,就可以在<code>Beego</code>中使用jade模板了。</p>
<h2>jade模板的使用</h2>
<p>首先定义一个页面<code>home.jade</code>:</p>
<pre><code>doctype html
html
head
title pageTitle
body
h1 jade
.content {{.content}}</code></pre>
<p>其中<code>{{.content}}</code>是需要替换的字段,<code>Controller</code>层代码:</p>
<pre><code>
func (c *MainController) Jade() {
c.Data["content"] = "this is jade template"
c.TplName = "home.jade"
}</code></pre>
<p>运行之后生成的页面代码:</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>pageTitle</title>
</head>
<body>
<h1>jade</h1>
<div class="content">this is jade template</div>
</body>
</html></code></pre>
<p>通过添加一个解析模板的引擎就可以在beego中使用jade的模板,beego从1.7.0之后开始支持复杂的模板引擎,不仅仅是对于jade的支持,也包括对于其他模板引擎的支持。<br>除了jade之外,在这个PR中推荐使用的是<a href="https://link.segmentfault.com/?enc=OsngXXpotYyveuEcsT5gyQ%3D%3D.v4pOc3IP6rw0VL24u%2BWxi4gWIPgHwnOyFkUD2r%2FLn74%3D" rel="nofollow">ace</a>HTML模板引擎。</p>
<h2>ace</h2>
<p><a href="https://link.segmentfault.com/?enc=ztxYX90Iljwr6XVrUIYBGA%3D%3D.2U%2F5YmQmaLa3pKkQwlrNxqEAih3kAER%2FOlvAQzu8Mz0%3D" rel="nofollow">ace</a>是一个Go语言的HTML模板引擎,它借鉴了<a href="https://link.segmentfault.com/?enc=O5pYREDbx4NIx%2FqAghaNlw%3D%3D.mo75yTBk8kPvPrTSql1mWlI18ipiz2vr5DMv3l9uKZA%3D" rel="nofollow">Slim</a>和<a href="https://link.segmentfault.com/?enc=7AePeZMHvVJlPZie8iJvFQ%3D%3D.PAPEbbIXPVV3R8vmO9Q7s%2BByZphPgoDzgxU4d7F9XwJiFQ%2Fl2UOW0irhaPBfWmVt" rel="nofollow">Jade</a>,在Go语言里有超高的人气。<br><code>ace模板</code>在beego中的使用与使用<code>Jade</code>类似,首先安装<code>ace</code>的依赖包:</p>
<blockquote><p>go get github.com/yosssi/ace</p></blockquote>
<p>在main函数中添加ace模板解析引擎:</p>
<pre><code>func addAceTemplate() {
beego.AddTemplateEngine("ace", func(root, path string, funcs template.FuncMap) (*template.Template, error) {
aceOptions := &ace.Options{DynamicReload: true, FuncMap: funcs}
aceBasePath := filepath.Join(root, "base")
aceInnerPath := filepath.Join(root, strings.TrimSuffix(path, ".ace"))
tpl, err := ace.Load(aceBasePath, aceInnerPath, aceOptions)
if err != nil {
return nil, fmt.Errorf("error loading ace template: %v", err)
}
return tpl, nil
})
}
</code></pre>
<p>注意使用ace模板需要指定一个<code>base.ace</code>,另外使用<code>ace.Load()</code>函数可以直接返回一个<code>template.Template</code>对象,不需要再做其他转换的工作。<br>添加完模板引擎之后就可以直接在views中添加.ace文件使用,新建一个<code>home.ace</code>文件:</p>
<pre><code>= doctype html
html lang=en
head
meta charset=utf-8
title Base and Inner Template
body
h1 ace
.content {{.content}}
</code></pre>
<p>同样的{{.content}}是也需要在<code>controller</code>层本替换成需要的内容,controller层代码:</p>
<pre><code>func (c *MainController)Ace() {
c.Data["content"] = "this is ace template"
c.TplName = "home.ace"
}</code></pre>
<p>示例代码地址:<a href="https://link.segmentfault.com/?enc=Et9M1fWdhXbuaf1x1u058w%3D%3D.e8efJ2hlGZBhU%2BJZrgxKnYZJEs%2FVn0s8mS22rnIpHEQo7LjhXlMl%2BySMMkfEcpW2" rel="nofollow">https://github.com/jjz/go/tree/master/template</a></p>
微信小程序开发:Flex布局
https://segmentfault.com/a/1190000007022502
2016-09-27T17:01:01+08:00
2016-09-27T17:01:01+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
2
<p>微信小程序页面布局方式采用的是<code>Flex</code>布局。<br><code>Flex</code>布局,是W3c在2009年提出的一种新的方案,可以简便,完整,响应式的实现各种页面布局。<br>Flex布局提供了元素在容器中的对齐,方向以及顺序,甚至他们可以是动态的或者不确定的大小的。<br>Flex布局的主要特征是能够调整其子元素在不同的屏幕大小中能够用最适合的方法填充合适的空间。<br><img src="/img/remote/1460000007022505" alt="flex布局" title="flex布局"></p>
<p>Flex布局的特点:</p>
<ul>
<li><p>任意方向的伸缩,向左,向右,向下,向上</p></li>
<li><p>在样式层可以调换和重排顺序</p></li>
<li><p>主轴和侧轴方便配置</p></li>
<li><p>子元素的空间拉伸和填充</p></li>
<li><p>沿着容器对齐</p></li>
</ul>
<p>微信小程序实现了<code>Flex</code>布局,简单介绍下<code>Flex</code>布局在微信小程序中的使用。</p>
<h2>伸缩容器</h2>
<p>设有<code>display:flex</code>或者<code>display:block</code>的元素就是一个<code>flex container</code>(伸缩容器),里面的子元素称为<code>flex item</code>(伸缩项目),<code>flex container</code>中子元素都是使用<code>Flex</code>布局排版。</p>
<ul>
<li><p><code>display:block</code> 指定为块内容器模式,总是使用新行开始显示,微信小程序的视图容器(view,scroll-view和swiper)默认都是<code>dispaly:block</code>。</p></li>
<li><p><code>display:flex</code>:指定为行内容器模式,在一行内显示子元素,可以使用<code>flex-wrap</code>属性指定其是否换行,<code>flex-wrap</code>有三个值:<em>nowrap(不换行)</em>,<em>wrap(换行)</em>,<em>wrap-reverse(换行第一行在下面)</em><br>使用<code>display:block</code>(默认值)的代码:</p></li>
</ul>
<pre><code> <view class="flex-row" style="display: block;">
<view class="flex-view-item">1</view>
<view class="flex-view-item">2</view>
<view class="flex-view-item">3</view>
</view></code></pre>
<p>显示效果:</p>
<p><img src="/img/remote/1460000007022506" alt="block" title="block"><br>改换成<code>display:flex</code>的显示效果:</p>
<p><img src="/img/remote/1460000007022507" alt="flex" title="flex"></p>
<p>可以从效果图看到<code>block</code>和<code>flex</code>的区别,子元素<code>view</code>是在换行显示(<code>block</code>)还是行内显示(<code>flex</code>)。</p>
<h2>主轴和侧轴</h2>
<p><code>Flex</code>布局的伸缩容器可以使用任何方向进行布局。<br>容器默认有两个轴:<em>主轴(main axis)</em>和<em>侧轴(cross axis)</em>。<br>主轴的开始位置为<code>主轴起点</code>(main start),主轴的结束位置为<code>主轴终点</code>(main end),而主轴的长度为<code>主轴长度</code>(main size)。<br>同理侧轴的起点为<code>侧轴起点</code>(cross start),结束位置为<code>侧轴终点</code>(cross end),长度为<code>侧轴长度</code>(cross size)。详情见下图:<br><img src="/img/remote/1460000007022508" alt="Flex-direction" title="Flex-direction"><br>注意,<code>主轴</code>并不是一定是<code>从左到右</code>的,同理<code>侧轴</code>也不一定是<code>从上到下</code>,主轴的方向使用<code>flex-direction</code>属性控制,它有4个可选值:</p>
<ul>
<li><p><code>row</code> :从左到右的水平方向为主轴</p></li>
<li><p><code>row-reverse</code>:从右到左的水平方向为主轴</p></li>
<li><p><code>column</code>:从上到下的垂直方向为主轴</p></li>
<li><p><code>column-reverse</code>从下到上的垂直方向为主轴</p></li>
</ul>
<p>如果水平方向为主轴,那个垂直方向就是侧轴,反之亦然。<br>四种主轴方向设置的效果图:<br><img src="/img/remote/1460000007022509" alt="示例图" title="示例图"></p>
<p>图中的实例展示了使用了不同的<code>flex-direction</code>值排列方向的区别。<br>实例代码:</p>
<pre><code><view >
<view class="flex-row" style="display: flex;flex-direction: row;">
<view class="flex-view-item">1</view>
<view class="flex-view-item">2</view>
<view class="flex-view-item">3</view>
</view>
<view class="flex-column" style="display:flex;flex-direction: column;" >
<view class="flex-view-item">c1</view>
<view class="flex-view-item">c2</view>
<view class="flex-view-item">c3</view>
</view>
</view></code></pre>
<p>运行效果:</p>
<p><img src="/img/remote/1460000007022510" alt="flex-direction" title="flex-direction"></p>
<h2>对齐方式</h2>
<p>子元素有两种对齐方式:</p>
<blockquote><p><code>justify-conent</code> 定义子元素在主轴上面的对齐方式<br><code>align-items</code> 定义子元素在侧轴上对齐的方式</p></blockquote>
<p><code>jstify-content</code>有5个可选的对齐方式:</p>
<ul>
<li><p><code>flex-start</code> 主轴起点对齐(默认值)</p></li>
<li><p><code>flex-end</code> 主轴结束点对齐</p></li>
<li><p><code>center</code> 在主轴中居中对齐</p></li>
<li><p><code>space-between</code> 两端对齐,除了两端的子元素分别靠向两端的容器之外,其他子元素之间的间隔都相等</p></li>
<li><p><code>space-around</code> 每个子元素之间的距离相等,两端的子元素距离容器的距离也和其它子元素之间的距离相同。<br><code>justify-content</code>的对齐方式和主轴的方向有关,下图以<code>flex-direction</code>为<code>row</code>,主轴方式是<code>从左到右</code>,描述<code>jstify-content</code>5个值的显示效果:</p></li>
</ul>
<p><img src="/img/remote/1460000007022511" alt="justify-content" title="justify-content"></p>
<p><code>align-items</code>表示侧轴上的对齐方式:</p>
<ul>
<li><p><code>stretch</code> 填充整个容器(默认值)</p></li>
<li><p><code>flex-start</code> 侧轴的起点对齐</p></li>
<li><p><code>flex-end</code> 侧轴的终点对齐</p></li>
<li><p><code>center</code> 在侧轴中居中对齐</p></li>
<li><p><code>baseline</code> 以子元素的第一行文字对齐</p></li>
</ul>
<p><code>align-tiems</code>设置的对齐方式,和侧轴的方向有关,下图以<code>flex-direction</code>为<code>row</code>,侧轴方向是<code>从上到下</code>,描述<code>align-items</code>的5个值显示效果:</p>
<p><img src="/img/remote/1460000007022512" alt="aign-items" title="aign-items"></p>
<p>有了主轴和侧轴的方向再加上设置他们的对齐方式,就可以实现大部分的页面布局了。</p>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=C2GS3bUGJRkLVzuwW5r0SA%3D%3D.MA7K4jlWT4iOfg01dTdV0m9WY4Do0T5CDWjfk4iCznJw2g6JRKHfbSnD9UnTPoG707WiglF%2B%2FXSQZLarkE6hjKkVfMVoYIxrK7WDgEhs%2FA4%3D" rel="nofollow">https://github.com/jjz/weixin...</a></p>
微信小程序开发:http请求
https://segmentfault.com/a/1190000007003240
2016-09-25T19:40:56+08:00
2016-09-25T19:40:56+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在微信小程序进行网络通信,只能和指定的域名进行通信,微信小程序包括四种类型的网络请求。</p>
<ul>
<li><p>普通HTTPS请求(wx.request)</p></li>
<li><p>上传文件(wx.uploadFile)</p></li>
<li><p>下载文件(wx.downloadFile)</p></li>
<li><p>WebSocket通信(wx.connectSocket)</p></li>
</ul>
<p>这里以介绍<code>wx.request</code>,<code>wx.uploadFile</code>,<code>wx.dowloadFile</code>三种网络请求为主</p>
<h2>设置域名</h2>
<p>要微信小程序进行网络通信,必须先设置域名,不然会出现错误:</p>
<blockquote><h5>URL 域名不合法,请在 mp 后台配置后重试</h5></blockquote>
<p>需要在微信公众平台的小程序中设置域名。<br>在微信小程序的<a href="https://link.segmentfault.com/?enc=JoZwKG9%2BzAghC63KtLUo2Q%3D%3D.kpOaVg1KDbZYFak9weoplFC9YcM1Ivr3nz3cztLwdP6ClqAR6Wz6lHVg9eLWUzLs" rel="nofollow">设置界面</a>可以看到设置选项:</p>
<p><img src="/img/remote/1460000007003243" alt="设置" title="设置"></p>
<p>选择<code>开发设置</code>:</p>
<p><img src="/img/remote/1460000007003244" alt="开发设置" title="开发设置"><br>可以看到服务器设置:</p>
<p><img src="/img/remote/1460000007003245" alt="服务器设置" title="服务器设置"><br>在这里可以设置对应四种网络访问的域名,每一种类型的网络请求需要设置一个域名,注意如果在这里设置域名为<code>https://example.com/api/</code>,那么<code>https://example.com/api</code>是无法调用的,必须加上后面<code>/</code>。</p>
<h2>http请求</h2>
<p>使用<code>wx.request</code>可以发起一个http请求,一个微信小程序被限制为<strong>同时只有5个网络请求</strong>。</p>
<pre><code>function queryRequest(data){
wx.request({
url:"https://example.com/api/",
data:data,
header:{
// "Content-Type":"application/json"
},
success:function(res){
console.log(res.data)
},
fail:function(err){
console.log(err)
}
})
}</code></pre>
<p>上面的代码会发送一个http get请求,然后打印出返回的结果。其中的参数也比较容易理解。</p>
<ul>
<li><p><code>url</code> 服务器的url地址</p></li>
<li><p><code>data</code> 请求的参数可以采用String <code>data:"xxx=xxx&xxx=xxx"</code>的形式或者Object <code>data:{"userId":1}</code>的形式</p></li>
<li><p><code>header</code> 设置请求的header</p></li>
<li><p><code>success</code> 接口成功的回调</p></li>
<li><p><code>fail</code> 接口失败的回调</p></li>
</ul>
<p>另外还有两个参数没有在代码里:</p>
<ul>
<li><p><code>method</code> http的方法,默认为GET请求</p></li>
<li><p><code>complete</code> 调用接口结束之后的回调,无论成功或者失败该接口都会被调用</p></li>
</ul>
<h2>上传文件</h2>
<p>上传文件的api为<code>wx.uploadFile</code>,该api会发起一个<code>http post</code>请求,其中的<code>Content-type</code>为<code>multipart/form-data</code>。服务器端需要按照该<code>Content-type</code>类型接收文件,示例代码:</p>
<pre><code>function uploadFile(file,data) {
wx.uploadFile({
url: 'http://example.com/upload',
filePath: file,
name: 'file',
formData:data,
success:function(res){
console.log(res.data)
},
fail:function(err){
console.log(err)
}
})
}</code></pre>
<p>其中的<code>url</code>,<code>header</code>,<code>success</code>,<code>fail</code>以及<code>complete</code>和普通的http请求是一样的。<br>这里有区别的参数是:</p>
<ul>
<li><p><code>name</code>文件对应的key,服务器端需要通过<code>name</code>参数获取文件</p></li>
<li><p><code>formData</code> http请求中可以使用的其他参数</p></li>
</ul>
<h2>下载文件</h2>
<p>下载文件的api为<code>wx.downloadFile</code>,该api会发起一个http get请求,并在下载成功之后返回文件的临时路径,示例代码:</p>
<pre><code>function downloadFile(url,typ,success){
wx.downloadFile({
url:url,
type:typ,
success:function(res){
if(success){
success(res.tempFilePath)
}
},
fail:function(err){
console.log(err)
}
})
}</code></pre>
<p>其中的<code>url</code>,<code>header</code>,<code>fail</code>,<code>complete</code>和<code>wx.uploadFile</code>的参数使用是一致的,其中有区别的参数是:</p>
<ul>
<li><p><code>type</code>:下载资源的类型,用于客户端自动识别,可以使用的参数<code>image/audio/video</code></p></li>
<li><p><code>success</code>:下载成功之后的回调,以<code>tempFilePath</code>的参数返回文件的临时目录:<code>res={tempFilePath:'文件路径'}</code><br>下载成功后的是临时文件,只会在程序本次运行期间可以使用,如果需要持久的保存,需要调用方法<code>wx.saveFile</code>主动持久化文件,实例代码:</p></li>
</ul>
<pre><code>function svaeFile(tempFile,success){
wx.saveFile({
tempFilePath:tempFile,
success:function(res){
var svaedFile=res.savedFilePath
if(success){
success(svaeFile)
}
}
})
}</code></pre>
<p>使用<code>wx.saveFile</code>保存临时文件到本地,提供给小程序下次启动时使用,其中的参数:</p>
<ul>
<li><p><code> tempFilePath</code> 需要被保存文件的路径</p></li>
<li><p><code>success</code> 保存成功的回调,返回保存成功的路径,使用<code>res.savedFilePath</code>可以获取保存成功的路径</p></li>
<li><p><code>fail</code> 失败的回调</p></li>
<li><p><code>complete</code>结束的回调</p></li>
</ul>
<h2>超时的设置</h2>
<p>在<a href="https://link.segmentfault.com/?enc=q%2BkPGDEBfaXuLfrZO%2BydJw%3D%3D.jJ1fzwyIviIsH9TCiqc68Pv4anFvoM%2FxvD%2BvxodiKLrI9t%2FOSarazDnZyFw0hpwS" rel="nofollow">微信小程序开发:MINA</a>中已经提到了在<code>app.js</code>中设置<code>networkTimeout</code>可以设置四种类型网络访问的超时时间:</p>
<pre><code>"networkTimeout":{
"request": 10000,
"connectSocket": 10000,
"uploadFile": 10000,
"downloadFile": 10000
}</code></pre>
<p>这里设置的超时时间对应着四种类型的网络请求。</p>
<p>源代码请参考:<a href="https://link.segmentfault.com/?enc=ADdfyppSGdtg2qjulrnq%2Fg%3D%3D.tq%2FjEYw%2BKnPyjgBuPJViWySeUbD4nvmBzpDAVi%2FczZnXTt2%2F5iYpBcSHNd8Mv7oiR7fcgdQibP3VZAtCptWSxg%3D%3D" rel="nofollow">https://github.com/jjz/weixin...</a></p>
微信小程序开发:MINA
https://segmentfault.com/a/1190000007000249
2016-09-25T09:58:22+08:00
2016-09-25T09:58:22+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
2
<p>MINA是微信开发小程序的框架:</p>
<blockquote><p>MINA的目标是通过尽可能简单,高效的方式让开发者可以在微信中开发具有原生APP体验的服务。</p></blockquote>
<p>运行MINA的项目必须要有<code>微信web开发者工具</code>和<code>微信小程序的AppID</code>,因为现在还处于内测阶段的原因,因此大部分数人还没有<code>AppID</code>,还好有大神已经破解了IDE,可以先体验下,详情请参考<a href="https://link.segmentfault.com/?enc=VkR4qkGaWwClbBd55IQYPQ%3D%3D.huARUPgHzdnMfb4%2FjkoUyIBsnd8aWk8O23h2iaf4tECrT17%2BeSbGgnq9dIX5kBbS" rel="nofollow">微信小程序开发资料收集</a><br>MINA框架中有四种类型的文件:</p>
<ul>
<li><p><code>.js</code>文件 基于JavaScript的逻辑层框架</p></li>
<li><p><code>.wxml</code> 视图层文件,是MINA设计的一套标签语言</p></li>
<li><p><code>.wxss</code> 样式文件,用于描述WXML的组件样式</p></li>
<li><p><code>.json</code> 文件,配置文件,用于单个页面的配置和整个项目的配置</p></li>
</ul>
<h2>目录结构</h2>
<p>为了减少配置项,小程序中一个页面中的四个文件必须要有相同的路径和文件名,使用<code>微信web开发者工具</code>新建一个项目,可以看到他的目录结构是这样的:</p>
<p><img src="/img/remote/1460000007000252" alt="目录结构" title="目录结构"></p>
<p>其中<code>app.js</code>是程序的入口,<code>app.json</code>是项目的配置文件,<code>app.wxss</code>是全局配置的样式文件,<code>logs</code>和<code>index</code>文件夹是是单个页面的文件,<code>utils</code>用来存放常用的工具类文件夹。</p>
<h3>app.js</h3>
<p><code>app.js</code>使用<code>App()</code>函数注册一个小程序,可以指定小程序的生命周期<br>小程序的<code>App()</code>生命周期中三个事件可以监听:<code>onLaunch</code>,<code>onShow</code>,<code>onHide</code>。</p>
<ul>
<li><p><code>onLaunch</code>:小程序加载完成之后调用,全局只触发一次</p></li>
<li><p><code>onShow</code>: 小程序启动,或者从后台到前台会触发一次</p></li>
<li><p><code>onHide</code>:小程序从前台到后台会触发一次</p></li>
</ul>
<p>例如:</p>
<pre><code>App({
onLaunch: function () {
console.log('App Launch')
},
onShow: function () {
console.log('App Show')
},
onHide: function () {
console.log('App Hide')
},
globalData: {
hasLogin: false
}
})</code></pre>
<p>其中<code>app.js</code>的<code>globalData</code>可以设置全局的变量,在一个页面中可以通过<code>getApp()</code>函数获取小程序的实例,使用App的<code>getCurrentPage()</code>可以获取到当前页面的实例。</p>
<h3>app.json</h3>
<p><code>app.json</code>是小程序的全局配置包括:页面的路径,窗口表现,设置网络超时,开发模式等...</p>
<ul><li><p>页面配置<code>pages</code>:设置页面的路径</p></li></ul>
<pre><code> "pages":[
"pages/index/index",
"pages/logs/logs"
]</code></pre>
<p>配置的<code>index</code>和<code>logs</code>两个页面的路径,在这里使用相对路径配置页面路径。</p>
<ul><li><p>窗口配置<code>windows</code>:用来配置状态栏的颜色,导航条的样式和颜色,标题,已经窗口的背景色:</p></li></ul>
<pre><code>"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}</code></pre>
<p>使用的Color为十六进制的颜色值,比如"#ffffff"<br>注意:<br>其中<code>navigationBarTextStyle</code>,导航栏的颜色仅支持<code>black/white</code>。<br>而<code>backgroundTextStyle</code>,下拉背景的样式仅支持<code>dark/light</code>。</p>
<ul><li><p><code>tabBar</code>: 设置tab应用,<code>tabBar</code>是一个数组,最少需要配置2个,最多能配置5个tab,tab按照数据的顺序排序:</p></li></ul>
<pre><code>"tabBar":{
"color":"#dddddd",
"selectdColor":"#3cc51f",
"borderStyle":"black",
"backgroundColor":"#ffffff"
,"list":[
{
"pagePath":"pages/index/index",
"iconPath":"image/wechat.png",
"selectedIconPath":"image/wechatHL.png",
"text":"主页"
},{
"pagePath":"pages/logs/logs",
"iconPath":"image/wechat.png",
"selectedIconPath":"image/wechatHL.png",
"text":"日志"
}]
}</code></pre>
<p>这里设置了两个tab页:<code>index</code>和<code>log</code>,效果如下:</p>
<p><img src="/img/remote/1460000007000253" alt="tab" title="tab"></p>
<ul><li>
<p><code>networkTimeout</code>设置网络请求的超时时间,小程序有四种类型的网络请求</p>
<ol>
<li><p><code>wx.request</code>普通的http请求,配置为<code>request</code></p></li>
<li><p><code>wx.connect</code> stock链接,配置为<code>connectSocket</code></p></li>
<li><p><code>wx.uploadFile</code>上传文件,配置为<code>uploadFile</code></p></li>
<li><p><code>wx.downloadFile</code>下载文件,配置为<code>downloadFile</code><br>配置单位为毫秒,例如:</p></li>
</ol>
</li></ul>
<pre><code>"networkTimeout": {
"request": 10000,
"connectSocket": 10000,
"uploadFile": 10000,
"downloadFile": 10000
}</code></pre>
<ul><li><p><code>debug</code>:开发工具中开启debug模式,在控制台面板上可以看到调试信息,我们也可以使用<code>console.log('onLoad')</code>输入log帮助我们调试程序。</p></li></ul>
<pre><code>"debug": true</code></pre>
<h3>app.wxss</h3>
<p><code>app.wxss</code>中定义的的样式为全局样式,作用在每一个页面,在page中定义的<code>.wxss</code>文件为局部样式,只作用在局部,局部样式中的定义会覆盖<code>app.wxss</code>中定义的样式。<br>样式的定义:</p>
<pre><code>.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}</code></pre>
<p>其中<code>200rpx</code>中的<code>rpx</code>是<code>reponslve pixel</code>,可以根据屏幕的宽度进行自适应,在<code>iPhone6</code>上<code>1rpx=0.5px=1</code>物理像素。微信小程序建议设计以<code>iPhone6</code>为准<br>样式的使用:</p>
<pre><code><view class="container">
</view>
</code></pre>
<h3>page</h3>
<p>使用Page()函数来注册一个页面,为其指定页面的初始数据,生命周期函数,事件处理等。</p>
<ul>
<li><p><code>data</code> 页面的初始数据,可以使用setData更新定义的数据</p></li>
<li><p><code>onLoad</code> 页面加载事件</p></li>
<li><p><code>onReady</code> 页面渲染完成</p></li>
<li><p><code>onShow</code> 页面显示</p></li>
<li><p><code>onHide</code> 页面隐藏</p></li>
<li><p><code>onUnload</code> 页面卸载</p></li>
</ul>
<p>例如:</p>
<pre><code>Page({
data: {
logs: []
},
onLoad: function () {
this.setData({
logs: (wx.getStorageSync('logs') || []).map(function (log) {
return util.formatTime(new Date(log))
})
})
}
})</code></pre>
<p>page另外使用的文件<code>.wxml</code>是页面文件,使用定义好一套标签语言,<code>.wxss</code>是局部样式文件,<code>.json</code>局部配置文件比如需要在一个单独的页面中设置他的<code>navigationBarTitleText</code>,可以在<code>.json</code>文件中设置:</p>
<pre><code>{
"navigationBarTitleText": "日志文件"
}</code></pre>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=HofL2e4LDI30ys%2B5QPBIQw%3D%3D.WGPvUseaZWf8Q18f2W%2BkRfNusRZccq4k%2FWKOB6EbFA4w9fbErKEQMW6FmvAl1mKz" rel="nofollow">https://github.com/jjz/weixin-mina</a></p>
微信小程序开发资料收集
https://segmentfault.com/a/1190000006983132
2016-09-23T09:53:53+08:00
2016-09-23T09:53:53+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<h2>相关资源</h2>
<p>IDE已经被破解<br>源代码地址:<a href="https://link.segmentfault.com/?enc=0UbNURcWNJYEOwSuImqE2Q%3D%3D.TVX8hDv1T7VFceNFghIrDHyGGuFGmeIaz0tWYWwtT%2FXPfTYiSXxvXBPBrgWe5Zxy" rel="nofollow">https://github.com/gavinkwoe/weapp-ide-crack</a></p>
<p>MINA api 文档地址:<a href="https://link.segmentfault.com/?enc=mixMnIS2cuPxmv5EjpvatQ%3D%3D.wvioHoQxnp43pQZuNDSJ8zDnXcAMjB1F4ya7Ewiq51E%3D" rel="nofollow">http://notedown.cn/weixin/api/</a></p>
<p>微信公众平台公布的文档:<br><a href="https://link.segmentfault.com/?enc=cRg%2FiNuniF27uEI4X65E4g%3D%3D.jJPiAy7Tlbksw3fwXecO0EMMBtXclVsMdcHXzhF6rhwOSvZUEmR2RalzwFfXwqT1xcyghN6OXjK3W3PGp8FSUQ%3D%3D" rel="nofollow">https://mp.weixin.qq.com/wiki...</a></p>
<p>开发工具下载地址:<br><a href="https://link.segmentfault.com/?enc=yviihYU6z5mOStCFtm0%2FWA%3D%3D.0Aic2i3O7ndqkLm5B7sC%2Fi%2FyYUNvojmDUnmMgeOUUp%2FfIeCG%2FTRSBwqCU5igIhqLGxq7uPrbg4SUcu%2Bd9XmWZM0G3UaxK3g4ebTLJ8FPNmE%3D" rel="nofollow">https://mp.weixin.qq.com/debu...</a></p>
<p>Mac版下载地址:<br><a href="https://link.segmentfault.com/?enc=UDDJBd00HJZB%2BhPt4k%2BRtg%3D%3D.FCHGQmoOpqOG0kT2uu1YhFU6%2BrlW4oGP8I40gZKtj%2Fg4booX65T8uxFy5fgGt%2BdCkW5aXnBxuABftsuydAtlLghuy5gKyvCSMpa3hMi3f3K3JmvMGWsZtGHMD1BaBTrj" rel="nofollow">http://dldir1.qq.com/WechatWe...</a></p>
<p>windows 64下载地址:<br><a href="https://link.segmentfault.com/?enc=lRBDQrN2Y9pGvGTd%2BzEAoQ%3D%3D.eh6YeeoXEioCgMdotU6dEiSkG0XtRM4MYlel91Njyz23L7VhaH1mX2%2FCvwbe2Aj3jKGULWbwagsCSkBGP3uFxtfXaZ7JJp%2Fo5IrfIKU81n7qh9ZhEVeNufOFQpqQrpf5" rel="nofollow">http://dldir1.qq.com/WechatWe...</a></p>
<p>Demo源代码: <br><a href="https://link.segmentfault.com/?enc=ODBwCot4O5vptKMnlVO%2Fow%3D%3D.1tO%2FIkNppY7lNlKBg8eU1P5xK4k97c4codOXUiD1n7oplf%2BoRvDMMlWjK3BDZT6L3CqA4OK79%2Bxw%2F0sqDAwt0k99T%2FRuJt2do4EWP6vHnqo%3D" rel="nofollow">https://mp.weixin.qq.com/debu...</a></p>
<h2>破解步骤</h2>
<p>下载好IDE,并安装<br>下载:<a href="https://link.segmentfault.com/?enc=9FJDGd95JopaXH8XX1NyYw%3D%3D.WrA2blWxS%2F7POkNM1TAqtuXGB0GVFnnQfNRcqqZ5qAY4jsvo9aFztYoq%2B7hGJQsm" rel="nofollow">https://github.com/gavinkwoe/...</a> 中的两个js :<code>createstep.js</code>和<code>projectStores.js</code><br>MAC上右键点击图标,选择<code>显示包内容</code></p>
<p>替换文件:</p>
<ul>
<li><p>/Resources/app.nw/app/dist/components/create/createstep.js</p></li>
<li><p>/Resources/app.nw/app/dist/stroes/projectStores.js</p></li>
</ul>
<p>运行<code>微信web开发者工具</code><br>使用微信扫描登录:<br><img src="/img/remote/1460000006983135" alt="登录" title="登录"></p>
<p>添加项目,<code>AppID</code>随便写一个,起一个项目名称,选择一个开发路径:</p>
<p><img src="/img/remote/1460000006983136" alt="新建项目" title="新建项目"></p>
<p>点击添加项目按钮:<br><img src="/img/remote/1460000006983137" alt="代码" title="代码"><br>:</p>
智能服装是一个伪命题
https://segmentfault.com/a/1190000006912985
2016-09-15T17:52:31+08:00
2016-09-15T17:52:31+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p><img src="/img/remote/1460000006912988" alt="思考" title="思考"></p>
<p>最近在新闻里能看到很多关于智能服装的报告比如下面这个:</p>
<blockquote>
<p> 听音乐、打电话、监测运动健康数据,智能服装有望成为可穿戴设备的下一个风口<br> 科技,正成为服装不可或缺的因素,甚至引领时尚潮流。</p>
<pre><code> 不久前,国内某知名服装品牌发布了新款智能夹克。与传统夹克不同,该产品设计了17个贴身隐形置物口袋。它拥有记步、控制音乐、遥控拍照、丢失提醒等功能,智能化的设计满足了商旅人士多场景的着装需求。
</code></pre>
</blockquote>
<p>这是一篇人民网的<a href="https://link.segmentfault.com/?enc=GwbpZoSP7qKp9PAt5jjfkg%3D%3D.bCCHtaaKbKcQRqusW5BMTQrXnPzdRfGGdFAT%2BjRFTbUE%2FrWmLdwsP98QyAvzEi2LkN1CGc7jMeCL1bKekQD7IA%3D%3D" rel="nofollow">报道</a> ,看着这篇报道,感觉这个智能夹克就应该是很好使用了,它这让我联想到前些日子的<a href="https://link.segmentfault.com/?enc=BYBd%2BSf3nw7hCcZ8YTJMsg%3D%3D.vqeyLHT2sThNpnfXUeCOGpVCkTbhurTDhlVCWZqc%2BuT6iPpEdfuDdiG%2B2tM%2BhRUug4y7xTP72PO%2FoQM58O%2BIZA%3D%3D" rel="nofollow">柒牌男装发布首款智能夹克</a>,其中的介绍:</p>
<blockquote><p>柒牌智能夹克共有长短两款,为了满足商旅人士多场景的着装需求,柒牌智能时尚夹克突破传统夹克设计,设计了多达17个贴身隐形置物口袋。该款夹克还有可拆卸连衣帽、遮光眼罩及瞬时充气枕,能够让商旅人士的长途旅行更安心舒适。此外,夹克还可以瞬间变身为单肩包,携带便捷。</p></blockquote>
<p>看着挺牛逼的,但为什么我会说智能服装是一个伪命题呢?</p>
<p>这款智能夹克并不能够单独实现上面的功能,它还需要一个<code>App</code>,才能完成。他并不是一个完整的现代意义上的计算机设备。<br>我们知道现代计算机的基础是<code>冯诺依曼体系结构</code>,必须具有如下的功能:</p>
<ul>
<li><p>运算器</p></li>
<li><p>控制器</p></li>
<li><p>存储器</p></li>
<li><p>输入设备</p></li>
<li><p>输出设备</p></li>
</ul>
<p>而智能服装无法具备上面的5大功能,你可以把他们看成一个<code>遥控器</code>,一个<code>输入设备</code>或者一个<code>输出设备(传感器输出数据)</code>,还有的能具备简单的存储功能,但是他并不是一个完成的<code>冯诺依曼体系结构</code>的计算机。</p>
<p>而且它还有其他的问题。</p>
<h2>需要用户记忆</h2>
<p>智能服装需要用户记忆的有两个大的问题:<code>功能开关</code>和<code>衣服本身</code></p>
<ol>
<li><p>功能<br>从上面的<code>17个贴身隐形置物口袋</code>就可以知道,在智能服装里面找个一个功能在那里是多么的麻烦,如果我要切一个歌曲,我就要想一下,这个功能开关在那里呢?而且还要保证设备本身和手机连着,那么我直接打开手机切个歌不是更方便吗?</p></li>
<li><p>衣服本身<br>比如有的智能服装使用使用<code>NFC</code>记录用户的命令,在一个扣子里面做一个<code>NFC</code>的芯片,用户只要使用支持NFC的手机接触这个芯片就可以把名片信息导入用户的手机。但是当我们真正使用这个扣子的时候会有一个致命的问题:<code>用户会换衣服</code>,用户如果换了一件普通的衣服上面并没有<code>NFC</code>芯片,根本就谈不上使用功能了,而用户如果换的是一个智能服装,也无法保证里面的<code>NFC</code>芯片记录的是名片信息,用户根本无法养成使用使用,就不会使用智能服装发送名片。</p></li>
</ol>
<p><code>听音乐、打电话</code>也会出现类似的问题,用户首先需要区分这件衣服是不是智能服装,再要确认这件智能服装的控制开发在那里,比如A厂商的智能服装开关可能在袖口,B厂商的可能在衣领。</p>
<h2>功能复杂 没有统一的GUI</h2>
<p>上面提到的智能夹克具有<code>17个贴身隐形置物口袋</code>,你想想看如果你打开一个App,下面的Tab页整整17个按钮你会怎么想,这还是好的,关键是它说到<code>隐形</code>,我还并不能完全理解<code>隐形置物口袋</code>是什么意思,但是可以肯定不是那么好找,这就意味着不好使用。<em>17个不好找,不好使用的按钮</em>。<br><code>GUI</code>即用户图形界面(Graphical User interface)是指采用图形方式实现的用户操作界面。<br>20世纪80年代苹果公司首先将GUI引入微机领域,微软公司也随后在PC了标准GUI,移动设备上面使用的是基于苹果的初代<code>Iphone</code>的<code>GUI标准</code>,这样的标准建立,大家就非常明白那里表示打开,那里表示关闭。<br>而智能服装没有统一的GUI标准,这就意味着你的口袋,袖口,扣子,衣领都有可能暗藏按钮,穿上智能服装你需要先熟悉下你的控制开发在那里。<br>智能服装做的功能越多,用户就越不知道如何使用,功能只能少,而且要很简单,嗯!最好不要多过普通衣服的功能。</p>
<h2>成本太高</h2>
<p>所有增加的任何一个芯片,任何一个NFC设备,任何一个蓝牙设备都会增加成本,而且不仅仅是芯片的成本,还能加大制作的难度,这都是成本。最终你增加的成本会反映到你的价格上。<br>你需要你的智能服装拥有<code>小米手环</code>的功能,你最起码也要付出小米手环里那个芯片的成本,小米手环带在手上就可以获取你的走路的步数,而如果你像在智能服装上面获取到用户的步数,你就需要用户穿上智能服装,需要每天必须穿一件含有记步芯片的智能服装,才能收集到这个数据,本来我只需要一个手环里面的芯片就可以解决的,现在我需要很多的衣服都需要带有这种芯片才能完成,这就极大的增加了用户的使用成本。</p>
<h2>传感器数据并不准确并且需要电</h2>
<p>目前的传感器收集到的数据并不准确,就拿<code>测量体温</code>来说,当前医院使用的最多而且最准确的是<code>水银体温计</code>。测量血压最准确的也是<code>水银血压计</code>。这就是以为着其他的电子设备的测量仪器并不是满足对于准备性的要求,对于一个不准确的数据,真正的实用价值有多大呢?<br>对于记步功能也是一样,现在的App有很多都有记步功能了,带着手机就可以测量了,反正大家都不是特别准确。<br>那么,用户真的需要一个智能服装吗?<br>另外获取到传感数据必须需要电,传感器数据也需要记录这也需要消耗电,怎么解决电的问题,每天回家到加给衣服冲电?<br>而且需要电还有带来下面的问题。</p>
<h2>电子芯片与水不相容</h2>
<p>电子芯片是不能接触水的,而作为一件衣服你要知道,你不仅仅要面对洗衣机的,还要面对雨淋,掉到水里,被人泼水....<br>这样智能服装的防水性就会要求很高,成本又会高涨。<br>另外智能服装如果需要用电的传感器,防水的级别又要提高,充电的还要防止充电口不能进水,换电子的需要做到电子的密封以及换电子之后的密封,这也会加大成本。</p>
<p>另外想说下对于材料的改进:</p>
<blockquote><p>为了预防潜在的寨卡病毒,一些参加里约奥运会的运动员穿上了特制的应用抗微生物技术的服装,以抵御蚊虫叮咬。</p></blockquote>
<p>这个只是材料的改进,你用<code>驱蚊草</code>做了件衣服能减少蚊子叮咬和<code>智能</code>真的不搭边。</p>
Mac OS制作Ubuntu安装U盘
https://segmentfault.com/a/1190000006835642
2016-09-07T06:23:26+08:00
2016-09-07T06:23:26+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
8
<p>采用U盘安装Ubuntu系统是目前比较常见的安装方式之一,在Windows上有制作安装U盘的工具(比如<code>Universal USB Installer</code>),那么在<code>Mac OS</code>上面如何制作安装U盘呢?<br>答案是<code>命令行</code>!</p>
<h2>hdiutil</h2>
<p>第一步,需要到<a href="https://link.segmentfault.com/?enc=JjMhMbyG11NBuHWTV2FU%2Fw%3D%3D.Dc4bf%2FbFeVpo0oBxHuD71KCQK3eOl21tYFVIeqxNWbQ%3D" rel="nofollow">Ubuntu</a>下载需要的Ubuntu的安装文件。<br>然后就需要使用第一个命令<code>hdiutil</code>。<br><code>hdituil</code>:是一个Mac OS上面处理镜像文件的<a href="https://link.segmentfault.com/?enc=Pyz%2FtmrNLloFkVzVC1eTcQ%3D%3D.D%2Bed3lefKslxjwnozdR5xe7PXad%2FOtpmFYEDFGBEUeiMc6Cyh2MAHLGulmb1jxNTnAwuNgq9Jhhlqrf2xeGwJL%2FupQ3IKN3BNwgTCyRfiPDbtxrtzdslQpnf4j6z2VCb2N7OjOCFECVuek0%2F2a4fcg%3D%3D" rel="nofollow">命令</a>,可以对镜像文件进行<code>制作,验证和转换</code>等...<br>我们知道<code>DMG</code>格式是Mac OS上常用的打包格式文件,需要把下载的Ubuntu安装文件(.iso)转换成(.dmg)格式的文件,方便在Mac OS上面进行操作,转换命令:</p>
<blockquote><p>cd Downloads/<br>hdiutil convert -format UDRW -o ubuntu.iso ubuntu-14.04.5-desktop-amd64.iso</p></blockquote>
<p><code>-format</code>为生成文件的权限,<code>UDRW </code>:表示转换成有<code>read/write</code>的权限的镜像。<br>等待转换完成即可~</p>
<h2>diskutil</h2>
<p>第二步需要需要对U盘进行操作,而<code>diskutil</code>就是用来对Mac OS的磁盘操作的命令。<br><code>diskutil</code>:操作本地磁盘,可以对磁盘进行<code>卸载,挂载</code>等操作。<br>列出当前挂载的磁盘:</p>
<blockquote><p>diskutil list</p></blockquote>
<pre><code>dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *251.0 GB disk0
1: EFI EFI 209.7 MB disk0s1
2: Apple_CoreStorage Macintosh HD 250.1 GB disk0s2
3: Apple_Boot Recovery HD 650.0 MB disk0s3
/dev/disk1 (internal, virtual):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_HFS Macintosh HD +249.8 GB disk1
Logical Volume on disk0s2
45CD1187-14DE-4203-9895-FBB1B3770F1E
Unencrypted
/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: Apple_partition_scheme *8.1 GB disk2
1: Apple_partition_map 4.1 KB disk2s1
2: Apple_HFS 2.4 MB disk2s2</code></pre>
<p>其中<code>/dev/disk2</code>就是U盘。<br>需要先卸载掉U盘,然后在把安装文件写入到U盘中,这样就需要用到卸载命令:</p>
<blockquote><p>diskutil unmountDisk /dev/disk2</p></blockquote>
<p>再次使用<code>diskutil list</code>命令就不会显示出<em>disk2</em>了。</p>
<h2>dd</h2>
<p>第三步,把安装文件写入U盘,这里需要使用命令<code>dd</code><br><code>dd</code>:是<code>Unix</code>和<code>类Unix系统</code>上的命令,作用就是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。</p>
<p>在进行拷贝之前,还需要做的一件时间,因为使用<code>hdiutil</code>转换的文件后缀名为<code>.dmg</code>,所以需要把文件重命名为<code>.iso</code>,在安装的时候系统才能够更好的识别。</p>
<blockquote><p>mv ubuntu.iso.dmg ubuntu.iso</p></blockquote>
<p>然后把安装文件拷贝到U盘中</p>
<blockquote><p>sudo dd if=./ubuntu.iso of=/dev/rdisk2 bs=1m</p></blockquote>
<p>这行命令必须使用<code>root</code>权限,</p>
<ul>
<li><p><code>if</code>:输入的文件名</p></li>
<li><p><code>of</code>:输出的文件名</p></li>
<li><p><code>bs</code>:是块大小,这里使用<code>1m</code>的块大小。<br>漫长的等待....</p></li>
</ul>
<pre><code>1052+1 records in
1052+1 records out
1104052224 bytes transferred in 249.471583 secs (4425563 bytes/sec)</code></pre>
<p>操作完成之后,安全地拔出U盘</p>
<blockquote><p>sudo eject /dev/rdisk2</p></blockquote>
<p>可以使用U盘进行Ubuntu的安装了!</p>
<h2>销毁安装数据</h2>
<p>安装完成之后,U盘上面的安装文件还在,这样会影响我们正常使用U盘。可以把U盘格式化一次,清除数据,也可以使用<code>dd</code>命令销毁磁盘数据:</p>
<blockquote><p>sudo dd if=/dev/urandom of=/dev/rdisk2</p></blockquote>
<p>使用随机数填充U盘,可以用来销毁数据,一般用于重要数据否则没有必要使用随机数填充。</p>
Ubuntu上安装Bitcoin Core
https://segmentfault.com/a/1190000006765749
2016-08-30T16:02:19+08:00
2016-08-30T16:02:19+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>在Ubuntu上安装Bitcoin Core除了在<a href="https://link.segmentfault.com/?enc=vsF5oa%2BaSFgckIbspTw3FQ%3D%3D.E3u%2FwGM78laz8VR32cRdWcBCPTYT9KGFNOjYPNX4iQU%3D" rel="nofollow">官网</a>上面直接下载安装包之外,还可以使用PPA的方式安装。</p>
<h2>PPA</h2>
<p>PPA:Personal Package Archives ,在Ubuntu上允许编译和发布一个apt repository。Bitcoin团队维护了一个apt repository ,地址:<br><a href="https://link.segmentfault.com/?enc=3csxTRuclP%2BZMKLwM%2B25jA%3D%3D.q%2BOo48Q7fG6LiIQZcelmDPQk0x6sUWSrUnNq8Wm7ScBt2PduVRa%2BeBILQkvdT2m49ugyRMAACEV3YhNxNO2wKQ%3D%3D" rel="nofollow">https://launchpad.net/~bitcoin/+archive/ubuntu/bitcoin</a>。 使用PPA的好处可以使用<code>shell</code>直接安装,自动解决依赖,还可以选择性的安装<code>bitcoind</code>和<code>bitcoin-qt</code>。</p>
<h2>安装Bitcoin Core</h2>
<p>首先需要添加bitcoin的源:</p>
<blockquote><p>sudo add-apt-repository ppa:bitcoin/bitcoin</p></blockquote>
<pre><code>Stable Channel of bitcoin-qt and bitcoind for Ubuntu, and their dependencies
More info: https://launchpad.net/~bitcoin/+archive/ubuntu/bitcoin
Press [ENTER] to continue or ctrl-c to cancel adding it
......
gpg: imported: 1 (RSA: 1)
OK</code></pre>
<p>添加源成功之后,需要更新下源:</p>
<blockquote><p>sudo apt-get update</p></blockquote>
<p>安装bitcoind:</p>
<blockquote><p>sudo apt-get install bitcoind</p></blockquote>
<p>可以选择性的安装<code>bitcoin-qt</code>,在<code>ubuntu-service</code>版本上是不需要<code>bitcoin-qt</code>的,如果是在<code>ubuntu-desktop</code>上面想使用<code>bitcoin-qt</code>的话可以选择安装:</p>
<blockquote><p>sudo apt-get install bitcoin-qt</p></blockquote>
<h2>运行bitcoind</h2>
<p>直接输入<code>bitcoind</code>命令可以让<code>bitcoind</code>在前台直接运行:</p>
<blockquote><p>bitcoind</p></blockquote>
<p>也可以采用后台运行的方式:</p>
<blockquote><p>bitcoind --daemon</p></blockquote>
<p>运行的时候我们可以使用命令查看<code>bitcoind</code>的运行情况:</p>
<blockquote><p>bitcoin-cli getinfo</p></blockquote>
<pre><code>{
"version": 120100,
"protocolversion": 70012,
"walletversion": 60000,
"balance": 0.00000000,
"blocks": 32,
"timeoffset": 0,
"connections": 6,
"proxy": "",
"difficulty": 1,
"testnet": false,
"keypoololdest": 1472539508,
"keypoolsize": 101,
"paytxfee": 0.00000000,
"relayfee": 0.00001000,
"errors": ""
}</code></pre>
<p>也可以直接查看<code>debug.log</code>,监控当前运行情况。<br>进入.bitcoin目录:</p>
<blockquote><p>cd $HOME/.bitcoin</p></blockquote>
<p>使用<code>tail</code>命令:</p>
<blockquote><p>tail -f debug.log</p></blockquote>
<h2>区块链数据</h2>
<p>在Linux中bitcoind的数据存在<code>$HOME/.bitcoin</code>目录下,该目录下有以下的文件。</p>
<ul>
<li><p><code>bitcoind.pid</code> bitcoind运行的进程文件</p></li>
<li><p><code>blocks</code> 区块链数据文件</p></li>
<li><p><code>chainstate</code> 区块链状态的数据库使用LevelDB存储</p></li>
<li><p><code>db.log</code> 数据库日志文件</p></li>
<li><p><code>debug.log</code> 运行时的日志文件</p></li>
<li><p><code>wallet.dat</code> 钱包文件</p></li>
</ul>
<p><code>bitcoind</code>数据在其它平台存在的地址。</p>
<blockquote><p>Windows</p></blockquote>
<ul><li><p>%APPDATA%Bitcoin 例如:C:UsersusernameAppDataRoamingBitcoin</p></li></ul>
<blockquote><p>Mac OSX</p></blockquote>
<ul><li><p>$HOME/Library/Application Support/Bitcoin/ 例如:/Users/username/Library/Application Support/Bitcoin</p></li></ul>
<p>打赏地址: 1LwaGJHSerrg14yXYciVLUFqWFRa9DqZE6</p>
React Native ios打包
https://segmentfault.com/a/1190000006668359
2016-08-21T08:00:26+08:00
2016-08-21T08:00:26+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
6
<p>开发React Native的过程成,js代码和图片资源运行在一个<code>Debug Server</code>上,每次更新代码之后只需要使用<code>command+R</code>键刷新就可以看到代码的更改,这种方式对于调试来说是非常方便的。<br>但当我们需要发布App到<code>App Store</code>的时候就需要打包,使用离线的js代码和图片。这就需要把JavaScript和图片等资源打包成离线资源,在添加到Xcode中,然后一起发布到<code>App Strore</code>中。<br>打包离线资源需要使用命令<code>react-native bundle</code>(注:文中使用的项目名称为<code>RNIos</code>)</p>
<h2>react-native bundle</h2>
<p>React Native的<code> react-native bundle</code>命令是用来进行打包的命令,<code>react-native bundle</code>的详细命令选项<a href="https://link.segmentfault.com/?enc=lh0BZ1a%2B7Dw67yYBePRx7A%3D%3D.3SP%2FE1KK8XZlougCF9YXce%2BLoblXSIm9Pd3ZOLTruUITgjn1Lmv12KG0M2oh4mRLc7WMY5jDOS8oDsma2qPdDlY4B1HXBwg22R2sDustCOOtAR%2FAGmqamnrpPDCJ%2FcFE" rel="nofollow">https://github.com/facebook/react-native/blob/master/local-cli/bundle/bundleCommandLineArgs.js</a>。<br>其中我们常使用的一线命令选项:</p>
<ul>
<li><p>--entry-file ,ios或者android入口的js名称,比如<strong>index.ios.js</strong></p></li>
<li><p>--platform ,平台名称(ios或者android)</p></li>
<li><p>--dev ,设置为false的时候将会对JavaScript代码进行优化处理。</p></li>
<li><p>--bundle-output, 生成的jsbundle文件的名称,比如<code>./ios/bundle/index.ios.jsbundle</code></p></li>
<li><p>--assets-dest 图片以及其他资源存放的目录,比如<code>./ios/bundle</code></p></li>
</ul>
<p>打包命令如下:</p>
<blockquote><p>react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ./ios/bundle/index.ios.jsbundle --assets-dest ./ios/bundle</p></blockquote>
<p>为了方便使用,也可以把打包命令写到npm script中:</p>
<pre><code>"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"bundle-ios":"node node_modules/react-native/local-cli/cli.js bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ./ios/bundle/index.ios.jsbundle --assets-dest ./ios/bundle"
},</code></pre>
<p>然后运行命令直接打包:</p>
<blockquote><p>npm run bundle-ios</p></blockquote>
<p>打包结果:</p>
<pre><code>...
transformed 360/360 (100%)
[8:56:31 PM] <END> find dependencies (3427ms)
bundle: start
bundle: finish
bundle: Writing bundle output to: ./ios/bundle/index.ios.jsbundle
bundle: Done writing bundle output
bundle: Copying 5 asset files
bundle: Done copying assets</code></pre>
<p>可以看到jsbundle和资源文件已经打包成功。</p>
<h2>添加资源</h2>
<p>离线包生成完成之后,可以在<em>ios</em>目录下看到一个<strong>bundle</strong>目录,这个目录就是<code>bundle</code>生成的离线资源。<br>需要在Xcode中添加资源到项目中,必须使用<strong>Create folder references</strong>的方式添加文件夹.</p>
<ol>
<li><p>Add Files to "RNIos"<br><img src="/img/remote/1460000006760661" alt="add Files" title="add Files"></p></li>
<li><p>选择<code>bundle</code>文件,在<em>option</em>中选择<strong>Create folder references</strong><br><img src="/img/remote/1460000006760662" alt="选择文件夹" title="选择文件夹"></p></li>
<li><p>添加到项目中的文件夹必须是蓝色<br><img src="/img/remote/1460000006760663" alt="文件夹必须是蓝色" title="文件夹必须是蓝色"></p></li>
</ol>
<h2>jsCodeLocation</h2>
<p>在ios中<code>AppDelegate</code>里可以看到设置JavaScript代码位置的代码:<br>Debug Server上的设置</p>
<pre><code>NSURL *jsCodeLocation;
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"RNIos"
initialProperties:nil
launchOptions:launchOptions];</code></pre>
<p>在开发的过程中可以在这里配置Debug Server的地址,当发布上线的时候,就需要使用离线的jsbundle文件,因此需要设置jsCodeLocation为本地的离线jsbundle。<br>设置离线的jsCodeLocation:</p>
<pre><code>jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"jsbundle"];</code></pre>
<p>离线包里的.jsbundle文件是经过优化处理的,因此运行效率也会比Debug的时候更高一些。</p>
<p>项目代码地址:<a href="https://link.segmentfault.com/?enc=ljR58QvqpPn%2FoFdGkkURKA%3D%3D.BM7mj5CybJfzWR58Lei9pzBQn3Td62lsG8Uh1Me277TS60MtApvUfHa%2B1oTEb0iRIt6Zl428ZLOwj%2FSSpXUX%2BA%3D%3D" rel="nofollow">https://github.com/jjz/react-native/tree/master/RNIos</a></p>
Android Studio NDK开发-其他编译选项
https://segmentfault.com/a/1190000006609985
2016-08-17T06:55:14+08:00
2016-08-17T06:55:14+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>NDK即要支持交叉编译,还需要支持各个Android版本的编译,还有一些其他情况的编译,比如c和c++等。NDK提供了一些编译选项,用来支持不同的编译需求。</p>
<p>以前的NDK开发都是在<code>Android.mk</code>和<code>Appliction.mk</code>中设置,现在在<code>gradle-experimental</code>的<code>android.ndk{}</code>设置编译选项。</p>
<p>其中包含了设置:编译器配置,NDK版本,Header头文件位置,gcc编译器选项等...</p>
<p>下面介绍下几个常用的编译选项。</p>
<h2>platformVersion</h2>
<p>大部分的App需要向下兼容,会设置<code>minSdkVersion</code>,从一个低版本的Sdk开始支持。同样的在低SDK版本上面运行的NDK也必须使用对应的NDK,就需要设置<code>platformVersion</code>。</p>
<p>设置<code>minSdkVersion</code>为14,对应的也需要设置<code>platformVersion</code>也是14:</p>
<pre><code>model{
android{
ndk{
....
platfromVersion=14
}
}
}</code></pre>
<p>也可以使用<code>paltformVersion "android-14"</code>设置NDK的版本。</p>
<h2>toolchain</h2>
<p><code>toolchain</code>是NDK下的一套文件系统和工具链,主要有GCC,编译adnroid的库和工具等,是本机编译工具或交叉编译工具。</p>
<p>在<em>gradle</em>中直接指定<code>toolchain</code>和<code>toolchainVersion</code>:</p>
<pre><code>model {
android {
......
ndk {
......
toolchain "clang"
toolchainVersion "3.5"
}
......
}
} </code></pre>
<h2>ABI</h2>
<p>Android系统目前支持其中七种不同的CPU架构:ARMv5,ARMv7,x86,MIPS,ARMv8,MIP64,x86_64,每一种都关联着一个相应的ABI。</p>
<p><code>Application Binary interface</code>应用程序二进制接口,定义了二进制文件(尤其是.so文件)是如何运行在相应的系统平台上的,从使用的指令集,内存到对齐到可用的系统函数库。在Android系统上,每一CPU架构对应一个ABI。<br>对应的.so文件会放在对应的ABI文件下面:</p>
<p><img src="/img/bVBTIM" alt="clipboard.png" title="clipboard.png"></p>
<p>一般情况下,考虑到x86系统的市场份额还太小,还有为了减少apk包的大小,可以指定支持的平台,使用<code>abiFilters</code>,可以选择项目支持的平台,比如只支持<code>armabi</code>和<code>armeabi-v7a</code>的配置:</p>
<pre><code>...
ndk {
...
abiFilters.addAll(['armabi', 'armeabi-v7a'])
...
}
...</code></pre>
<h2>ldLibs</h2>
<p>使用NDK开发Android应用时,会使用到系统自带的库,在之前的<code>*.mk</code>中的<code>LOCAL_LDLIBS</code>就是用来引用系统库的,现在使用在<code>Android studio</code>中可以配置<code>ldLibs</code>引用系统库。</p>
<p>例如在项目中链接android的<code>log</code>:</p>
<pre><code>ndk {
...
ldLibs.addAll(['log'])
}</code></pre>
<h2>CFlags和CppFlags</h2>
<p>CFlag表示用于C编译器的选项。</p>
<p>CppFlags用来表示用于c++的编译器选项.</p>
<p>使用ldLibs告诉了链接器要链接那些库文件,而在CFlags和CppFlags可以用来指定.h文件的路径。</p>
<p>例如指定一个.h文件的目录:</p>
<pre><code> ndk {
...
CFlags.addAll(['-I/usr/local/ssl/android-14/include'])
...
}</code></pre>
<p><code>CFlag</code>也是gcc的编译选项,比如使用<code>-Wall</code>表示打开警告开发,<code>-g</code>是要生成调试信用,生成的可执行文件具有和源代码关联的可调试信息。</p>
<h2>stl</h2>
<p>stl是设置c++的运行时库的选项,默认使用的是<code>libstdc++</code>最小支持的c++库。</p>
<p>如果要使用<code>gnustl_static</code>(静态链接gnustl版本的stl)可以设置为:</p>
<pre><code> ndk {
...
stl = 'gnustl_static'
cppFlags.addAll(['-std=c++11'])
...
}</code></pre>
<p>在<a href="https://link.segmentfault.com/?enc=96vZk7z4p2JZsIw8sa0N0Q%3D%3D.TphJo2%2BIuduR1JNMT6o982%2BfsvignO7XD93SfliIfu%2BD%2Bch1ionQEgs4iVK32hKvk1rissgkCwAJwqbo43FzeA%3D%3D" rel="nofollow">这里</a>可以看到NDK支持的对应的c++运行库,建议使用动态方法链接stl,这样可以减少apk的大小。</p>
React Native 升级到版本到0.25.1
https://segmentfault.com/a/1190000006231854
2016-08-10T20:52:33+08:00
2016-08-10T20:52:33+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>React Native的更新策略是两个星期迭代一个新版本,使用的React Natvie没有多长时间就需要升到最新版本了,这里介绍下如何升级到版本0.25.1以及其以上。</p>
<p>React Native 25,一个显著的变化是<em>import React</em>的时候不再从<code>react-native</code>中导入,而是从<code>react</code>中导入。所以首先需要添加<code>react</code>的依赖。</p>
<h2>使用React</h2>
<p>首先要添加<code>React</code>的依赖,如果把react-natie的版本直接升高到当前版本,比如:<code>"react-native": "0.31.0"</code>。<br>使用<code>npm install </code> 安装依赖包的时候,会提示错误:</p>
<pre><code>npm WARN react-native@0.31.0 requires a peer of react@~15.2.1 but none was installed.</code></pre>
<p>就需要我们添加对于<code>React</code>的依赖,可以使用命令添加<code>React</code>的依赖:</p>
<blockquote><p>npm install -save react@~15.2.1</p></blockquote>
<p>也可以在<code>package.json</code>中添加依赖:</p>
<pre><code>{
"name": "rn310",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "^15.2.1",
"react-native": "0.31.0"
}
}</code></pre>
<p>在运行<code>npm install</code>就可以自动下载依赖了。</p>
<h2>import React from 'react'</h2>
<p>React Native 25之后,除了React不再从<code>react-native</code>中引入之外,还包括Component,PropTypes,Children等...<br>这里要注意,旧版本的React是从<code>react-native</code>中import的。<br>如果升级完依赖之后,直接运行项目之后会得到一个报错信息:</p>
<pre><code>Seems you're trying to access 'ReactNative.Component' from the 'react-native' pakeage.
Perhaps you meant to access 'React.Component' from the 'React' package instead?
For example, instead of :
import React, { Component, View } from 'react-native';
you should now:
import React, { Component } from 'react';
import { View } from 'react-native';
.....</code></pre>
<p><img src="http://upload-images.jianshu.io/upload_images/22188-92f0fcbb799b462a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="import React" title="import React"></p>
<p>提示的内容就是告诉我们需要把Component,React等从包<code>react</code>中引入,不能再从<code>react-native</code>中引入。<br>如果要更新引入,基本上要修改所有的文件,还好我们从<a href="https://link.segmentfault.com/?enc=Yi4mlkNFcm42e0Q48ToquQ%3D%3D.M2hfnF0a1WNNu4ahLQ2PhmlhY%2BBj6x%2Fz1oesfl6bdJ10Q%2ByVb76LhJLbjD1l8VvO4m0w8mFZ20fCF%2F5%2BYqEfMw%3D%3D" rel="nofollow">更新说明</a>中可以看到有工具来做这个事情。<br><a href="https://link.segmentfault.com/?enc=iaQnibzizavv9p%2B225ZreA%3D%3D.bGv%2Fb0XSfWZYXVnxfG%2F3Hzawh2UR0r8txQuS9VxZTpPwpQnCoVXYM9LQHLh%2BLhe3twONDsizLq5TLiCqX8hNmw%3D%3D" rel="nofollow">codemod-RN24-to-RN25</a>是一个升级项目文件支持<code>React Native 25</code>的工具,更改文件的<code>import</code>,让需要引入<code>react</code>的文件能够正确的<code>import</code>。<br>使用方法:</p>
<ol>
<li>
<p>安装<code>jscodeshift</code></p>
<pre><code>npm install -g jscodeshift</code></pre>
</li>
<li>
<p>clone项目,</p>
<pre><code>git clone git@github.com:sibeliusseraphini/codemod-RN24-to-RN25.git</code></pre>
</li>
<li>
<p>copy <em>transform.js</em></p>
<pre><code>cd codemod-RN24-to-RN25
cp transform.js `YOUR_PROJECT_PATH`</code></pre>
</li>
<li>
<p>运行命令转换文件</p>
<pre><code>cd `YOUR_PROJECT_PATH`
jscodeshift transform.js</code></pre>
</li>
</ol>
<p>等待运行完成之后可以看到<code>React</code>以及<code>Component</code>等都能被正确的引用到<code>react</code>包下。</p>
React Native错误:in next release empty section headers ...
https://segmentfault.com/a/1190000006227814
2016-08-10T15:39:10+08:00
2016-08-10T15:39:10+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>升级<code>React Native</code>到最新版本出现了一个<code>Warning</code>:</p>
<pre><code>Warning:In next release empty section headers will be rendered.
In this release you can use 'enableEmptySections' flag to render empty section headers</code></pre>
<p>这个错误出现在<code>ListView</code>中,在以后的版本中才会实现空的<code>section headers</code>作为默认值,当前版本并没有支持。<br>如果我们不需要使用headers的话,可以禁止<strong>EmptySections</strong>。<br>解决方法:</p>
<pre><code> <ListView
style={styles.listView}
dataSource={this.state.dataSource}
enableEmptySections={true}
/></code></pre>
<p>参考文档:<br><a href="https://link.segmentfault.com/?enc=G%2FZ8hx%2FbCpJxzAo1CZDGjA%3D%3D.QHhgX4yKKA7ha37h2PfEk0K3B7a5wPnogRQJFweNqbwtzOvk5AJK%2FWxtB4sM3qmIDJ7ZtFvK20JIb0uviIc1LBHrNqWHHlXGFQTGmYKjQWM%3D" rel="nofollow">http://facebook.github.io/react-native/docs/listview.html#enableemptysections</a></p>
react-native调用ios native方法-回调
https://segmentfault.com/a/1190000006219306
2016-08-09T19:19:04+08:00
2016-08-09T19:19:04+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>上一篇中介绍了使用<a href="https://link.segmentfault.com/?enc=dD4lQgQBxvcratNdNZFNWQ%3D%3D.j%2FLTB3yrPND1OJy9cvqOdBd1gHGgdxKbMf4PqYQg5r4ydePYRddqtSl3mI2kJld9" rel="nofollow">react-native调用ios native方法</a>,在真实的使用场景中,不仅仅只是调用下Native的方法,还需要对结果进行处理,Native处理完之后返回结果再回调会JavaScript中进行操作和处理。<br>这样就需要使用JavaSctipt的回调函数,对结果进行处理。在React Native中Object-c有两种方式的回调:<code>RCTReponseSenderBlock</code>和<code>Promises</code>。</p>
<h2>RCTReponseSenderBlock</h2>
<p>在JavaScript和Object-C的参数列表,有一类参数叫做<code>RCTReponseSenderBlock</code>对于JavaScript的<strong>Function</strong>,这个就是JavaScript调用Object-C的Callback(回调函数)。</p>
<blockquote><ul><li><p><code>RCTReponseSenderBlock</code>是在<code>RCTBridgeModule.h</code>定义的block.<br>完整的定义:</p></li></ul></blockquote>
<pre><code>typedef void (^RCTResponseSenderBlock)(NSArray *response);</code></pre>
<p><code>RCTReponseSenderBlock</code>定义个一个Object-C Bridge的操作,返回给JavaScript一个callback的方法。</p>
<p>他的参数是一个<code>NSArray</code>。其中第一个参数是<code>error</code>代表着错误信息,如果没有错误传入<em>null</em>,后面的参数传入自定义的内容。<br>具体实例:<br>先给<code>UIAlertView</code>添加两个按钮,在点击按钮之后使用<code>RCTResponseSenderBlock</code>返回JavaScript。<br>先实现<code>UIAlertViewDelegate</code>:</p>
<pre><code>@interface RNIOSAlert : NSObject<RCTBridgeModule,UIAlertViewDelegate>
@end</code></pre>
<p>定义一个变量用来保存参数:</p>
<pre><code>@implementation RNIOSAlert{
RCTResponseSenderBlock _alertCallback;
}
@end</code></pre>
<p>在<code>clickedButtonAtIndex</code>方法中处理结果并调用回调函数:</p>
<pre><code>
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex==0) {
_alertCallback(@[@"cancel",]);
}else{
_alertCallback(@[[NSNull null],@1]);
}
}</code></pre>
<p>最后定义带有回调参数的Native方法:</p>
<pre><code>
RCT_EXPORT_METHOD(showAlertAndCallback:(RCTResponseSenderBlock)callback){
_alertCallback=callback;
UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:@"react-native" message:@"是否继续?" delegate:self cancelButtonTitle:@"关闭" otherButtonTitles:@"继续", nil];
[alertView show];
}</code></pre>
<p>最在JavaScript中调用Native方法,并处理回调:</p>
<pre><code> _alertCallback() {
RNIOSAlert.showAlertAndCallback(function (err, datas) {
if (err) {
console.warn('err', '已取消');
} else {
console.warn('data', '请继续');
}
});
}</code></pre>
<p>每次关闭<code>UIAlertView</code>都可以看到JavaScript处理的结果。</p>
<h2>Promises</h2>
<p><code>Promises</code>是<code>ES6</code>中的特性,它的目的是统一为JavaScript提供异步编程的接口,避免Callback地狱,解决了Callback的层层嵌套。更加容易的对异步操作进行控制。<br>在React Native中对<code>Promises</code>有很完善的支持,调用Object-C 的Native方法的时候,也可以Promise的方式让代码执行从Object-C 回到JavaScript中。<br>先看Promises的两个状态。</p>
<blockquote><ul><li><p>Resolve和Reject</p></li></ul></blockquote>
<p><code>Resolve</code>和<code>Reject</code>分量是Promise的两种状态,表示已解决和已拒绝,<code>Resolve</code>是正常的执行结果,而<code>Reject</code>会触发<code>catch</code>操作。<br>在Object-C与之相对应的是:<code>RCTPromiseResolveBlock</code>和<code>RCTPromiseRejectBlock</code>,两个都是定义好的Object-C bridge。</p>
<p><code>RCTPromiseResolveBlock</code>的实现:</p>
<pre><code>typedef void (^RCTPromiseResolveBlock)(id result);</code></pre>
<p>以<code>id</code>为参数,当然传参必须是Object-c和JavaScript定义好的参数。<br><code>RCTPromiseRejectBlock</code>的实现:</p>
<pre><code>typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);</code></pre>
<p><code>RCTPromiseRejectBlock</code>使用了NSError,还可以传入自定义的<em>code</em>以及<em>message</em>。</p>
<ul><li><p>使用Promise实现回调<br>首先要定义两个变量用来保存参数:</p></li></ul>
<pre><code>@implementation RNIOSAlert{
RCTPromiseResolveBlock _resolveBlock;
RCTPromiseRejectBlock _rejectBlock;
}</code></pre>
<p>在定义提供给JavaScript调用的Native函数:</p>
<pre><code>RCT_REMAP_METHOD(alertUserPromise, resolver:(RCTPromiseResolveBlock)resolver rejecter:(RCTPromiseRejectBlock)reject){
_resolveBlock=resolver;
_rejectBlock=reject;
UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:@"react-native" message:@"使用Promise?" delegate:self cancelButtonTitle:@"关闭" otherButtonTitles:@"继续", nil];
[alertView show];
}</code></pre>
<p>这里使用<code>RCT_REMAP_METHOD</code>宏定义Native,他的第一个参数是方法名,后面的参数是方法的实现,在JavaScript调用Promise时并不需要在方法名中体现。<br>处理结果并使用回调:</p>
<pre><code>-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex==0) {
NSError * err=[NSError errorWithDomain:@"test" code:0 userInfo:nil];
_rejectBlock(@"0",@"cancel",err);
}else{
_resolveBlock(@[@1]);
}
}</code></pre>
<p>JavaScript中调用该方法:</p>
<pre><code> _alertUsePromise() {
RNIOSAlert.alertUserPromise().then((datas)=> {
console.warn('data', datas);
}).catch((err)=> {
console.warn('err', err);
});
}</code></pre>
<p>这里的then处理的是<code>Resovle</code>状态的结果,而catch处理的是<code>Reject</code>状态的结果。<br>也可以使用async/await实现</p>
<pre><code> async _alertPromise() {
try {
var datas = await RNIOSAlert.alertUserPromise();
console.warn('data', datas);
} catch (e) {
console.warn('err', e);
}
}</code></pre>
<p><code>async/await</code>是两个关键词,用来把<code>Promises</code>的思想融入到语言本身。使用他们就不再需要写catch这样的伪同步的代码,直接使用try/catch/return这样的关键词就可以了.<br>Promises对于回调的使用很便利,尽量避免了JavaScript的回调地狱。</p>
<p>代码地址:<a href="https://link.segmentfault.com/?enc=ADX3%2BYt%2By2Zma6QVKtUzdw%3D%3D.i91rggROyw8pZq6I0gCUKN0Ns7qLaY1C2J2JqJxtnV%2FoEufHsoinpg%2BHbvkPXI%2BBr23j%2BvwRAI5%2FGwlC8RT%2BEw%3D%3D" rel="nofollow">https://github.com/jjz/react-...</a></p>
react-native调用ios native方法
https://segmentfault.com/a/1190000006207887
2016-08-08T19:12:21+08:00
2016-08-08T19:12:21+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p>React Native在设计之初,就考虑到了React Native提供的API,不能够完全的覆盖平台对应的所有API.因此在React Native中可以方便的调用Native的方法,Android上面对应Java方法,IOS上对应Object-C方法。<br>有的时候在处理数据库,多线程上面,使用Native加的方便。<br>下面就以调用IOS系统的<code>Alert</code>为例,看下怎么使用JavaScript代码调用Object-C的Native方法的。</p>
<h2>RCTBridgeModule</h2>
<p><code>RCT</code>是ReaCT的缩写,React Native中Object-C相关的命名均以RCT开头。<br><code>RCTBridgeModule</code>是定义好的<code>protocol</code>,实现该协议的类,会自动注册到Object-C对应的Bridge中。<br>Object-C Bridge上层负责与Object-C通信,下层负责和JavaScript Bridge通信,而JavaScript Bridge负责和JavaScript通信.<br>这样,通过Object-C Bridge和JavaScript Bridge就可以实现JavaScript和Object-C的相互调用。<br>先要定义一个类:<code>RNIOSAlert</code>用来现实<code>RCTBridgeModule</code>协议。</p>
<pre><code>#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "RCTBridgeModule.h"
@interface RNIOSAlert : NSObject<RCTBridgeModule>
@end
</code></pre>
<h2>RCT_EXPORT_MODULE</h2>
<p>所有实现<code>RCTBridgeModule</code>的类都必须显示的<code>include</code>宏命令:<code>RCT_EXPORT_MODULE()</code>。<br><code>RCT_EXPORT_MODULE</code>的作用是:自动注册一个Module,当Object-c Bridge加载的时候。这个Module可以在JavaScript Bridge中调用。<br><code>RCT_EXPORT_MODULE</code>宏命令的定义:</p>
<pre><code>#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
</code></pre>
<p>可以看到<code>RCT_EXPORT_MODULE</code>接受字符串作为其Module的名称,如果不设置名称的话默认就使用类名作为Modul的名称。<br>引入<code>RCT_EXPORT_MODULE</code>:</p>
<pre><code>
#import "RNIOSAlert.h"
@implementation RNIOSAlert
RCT_EXPORT_MODULE();
@end
</code></pre>
<h2>RCT_EXPORT_METHOD</h2>
<p><code>RCT_EXPORT_METHOD</code>是用来定义被JavaScript调用的方法的宏。<code>RCT_EXTERN_METHOD</code>调用了宏<code>RCT_EXTERN_REMAP_METHOD</code>,最终调用宏<code>RCT_EXTERN_REMAP_METHOD</code>。<br><code>RCT_EXTERN_REMAP_METHOD</code>的代码实现:</p>
<pre><code>#define RCT_EXTERN_REMAP_METHOD(js_name, method) \
+ (NSArray<NSString *> *)RCT_CONCAT(__rct_export__, \
RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
return @[@#js_name, @#method]; \
}</code></pre>
<p>它的作用是在<code>RCT_EXPORT_MODULE</code>定义的Module下面,定义一个可以被JavaScript调用的方法。<br><code>RCT_EXPORT_MODULE</code>的使用,需要写入方法名,参数以及完整的实现,例如:</p>
<pre><code>#import "RNIOSAlert.h"
@implementation RNIOSAlert
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(show:(NSString *)msg){
UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:@"react-native" message:msg delegate:nil cancelButtonTitle:@"关闭" otherButtonTitles:nil, nil];
[alertView show];
}
@end
</code></pre>
<h2>JavaScript调用</h2>
<p>在JavaScript中调用Object-C定义的方法,需要先导入<code>NativeModules</code>,再使用<code>RNIOSAlert</code>:<br>完整代码如下:</p>
<pre><code>import {
StyleSheet,
Text,
View,
NativeModules,
TouchableOpacity
} from "react-native";
var RNIOSAlert = NativeModules.RNIOSAlert;
class RNIos extends Component {
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={()=>RNIOSAlert.show('from react native ')}>
<Text>Alert</Text>
</TouchableOpacity>
</View>
);
}
}</code></pre>
<p>成功调用<code>Alert</code>:</p>
<p><img src="/img/remote/1460000011643752" alt="Alert" title="Alert"></p>
<h2>参数</h2>
<p><code>RCT_EXPORT_METHOD</code>支持需要JSON所支持的数据类型,JavaScript中的参数与Object-C的参数的对应关系。</p>
<ul>
<li><p>string -> NSString</p></li>
<li><p>number -> (NSInteger,float,double,CGFloat,NSNumber)</p></li>
<li><p>boolean -> (BOOL,NSNumber)</p></li>
<li><p>array -> NSArray</p></li>
<li><p>object -> NSDictionary</p></li>
<li><p>function -> RCTResponseSenderBlock</p></li>
</ul>
<p>另外React Navite还提供了<code>RCTConvert</code>,详情的代码可以参照 <a href="https://link.segmentfault.com/?enc=rqN7wQrVsTMXO6DaTaVb8g%3D%3D.xw33%2B8ospJrY%2FIJQPbUdiqbfCNJSX1Mr9ZL%2FrIShH%2FdYcK7CCDK9ICvWePKczUOlnAGZTyE1wZXHzkHVg6Ej2jX6Mf%2FIl0Wto%2B4NdWsrC5Y%3D" rel="nofollow">https://github.com/facebook/react-native/blob/master/React/Base/RCTConvert.h</a>,他的作用可以把传入的参数转换为需要的数据类型。<br>比如我们在Object-C中定义一个方法,该方法接收<code>NSDictionary</code>参数:</p>
<pre><code>
RCT_EXPORT_METHOD(showTime:(NSDictionary*)dict){
NSDate * date =[RCTConvert NSDate:dict[@"time"]];
UIAlertView * alertView=[[UIAlertView alloc] initWithTitle:@"react-native" message:[date description] delegate:nil cancelButtonTitle:@"关闭" otherButtonTitles:nil, nil];
[alertView show];
}
</code></pre>
<p>这里使用<code>RCTConvert</code>直接把<strong>NSDictionary</strong>中的值转换为NSDate.<br>在JavaScript中的调用<code>showTime</code>方法:</p>
<pre><code>......
<TouchableOpacity onPress={()=> {
var date = new Date();
RNIOSAlert.showTime(
{
time: date.getTime()
}
)
}}>
<Text>Time</Text>
</TouchableOpacity>
......</code></pre>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=iXg3Wtq5tp3xzvDjSZU7BQ%3D%3D.vo0qO8u%2F%2BFiV9PlbOcPdjA8A6a5tbe9X6NFqRM9W29wRUb%2FyI6c2FMGMf1jdZhmTFBg3e7a32sLCSIEqJpz0kg%3D%3D" rel="nofollow">https://github.com/jjz/react-...</a></p>
Android代码规范-命名规范
https://segmentfault.com/a/1190000005893399
2016-07-06T19:18:06+08:00
2016-07-06T19:18:06+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>编码规范对于程序员尤为重要,可以有效的帮助我们进行<code>code review</code>,提高代码的可读性,让其他人更快的理解代码。<br>一个软件的生命周期中,80%的花费都在于维护以及新功能的迭代,很多的时候都需要阅读代码,读自己以前写的代码,读别人写的代码,这样规范代码的优势就显示出来了,符合规范的代码可以让人简单快速的理解理解代码的意图。<br>代码规范先从命名规范开始,Android的命名规范主要涉及:</p>
<ul>
<li><p>Java源代码</p></li>
<li><p>xml文件</p></li>
<li><p>图片等资源文件</p></li>
</ul>
<p>先从Java源代码开始说起,要说Java源代码不得不先说下包名的命名规范。</p>
<h2>包名</h2>
<p>Android包名的命名规则,Andorid的包名一般采用域名的反转,单词全小写。<br>比如域名为<code>www.example.com</code>的包名为<code>com.example</code>,省略<em>www</em>。<br>包名开始是一个顶级域名,比如<strong>com</strong>,<strong>cn</strong>,<strong>org</strong>等,包名使用<code>.</code>做为分隔符。第二位一般是二级域名,也可以根据不同机构各自的命名。<br>后面的命名可以用部门,项目等进行区分(也可以没有),例如:</p>
<blockquote><p>com.example.project</p></blockquote>
<p>在项目内可以根据功能不同,按照模块划分不同的包名,<code>com.example.project.user</code>表示用户模块。<br>也可以根据层级的不同而划分不同的包名,比如:<code>com.example.prokect.activity</code>,就是Acitivity相关的包。<br>当然也可以在不同层级里面再按照模块划分包名,比如:<code>com.example.project.activity.user</code>,表示和用户有关的Activity。</p>
<hr>
<p>总结,包名一般是以反转域名开始,后面跟有项目名称(缩写,也可以没有)。<br>后面可以采用的区分包名方式:</p>
<ul>
<li><p>按照模块 <code>com.example.project.user</code></p></li>
<li><p>按照层级区分 <code>com.example.project.activity</code></p></li>
<li><p>层级下也可以在区分模块 <code>com.example.project.activity.user</code></p></li>
</ul>
<h2>类和接口</h2>
<p>类名是一个或多个单词组成,采用大驼峰命名,尽量要使类名简洁且利于描述,例如:<code>SignInActivity</code>,类名规则如下:</p>
<ul>
<li><p>大驼峰命名</p></li>
<li><p>简洁而富有表达性</p></li>
<li><p>尽量不使用缩写(广泛使用的单词除外,比如URL,XML...)</p></li>
<li><p>多单词中采用 <code>名词+动词</code>的方式命名: <code>LocationManage</code></p></li>
<li><p>对于缩写单词要全部大写比如:<code>XMLManage</code></p></li>
</ul>
<p>一个类如果继承了Android的组件,需在使用该组件的名称作为后缀,这样容易区分该类的作用,比如:<code>SgnInActivity</code>,<code>UserInfoFragment</code>,<code>FileUploadService</code>...<br>接口一般使用<strong>I</strong>开头,采用大驼峰命名规则,比如:<code>IPullToRefresh</code>。</p>
<h2>变量</h2>
<p>Android变量分为三种:成员变量,静态变量和常量。</p>
<ul><li><h4>成员变量</h4></li></ul>
<p>成员变量一般采用小驼峰命名规则,第一单词的首字母小写,其后的首字母大写。变量名一般不使用<em>_</em>和<em>$</em>开头。例如:</p>
<blockquote><p>private Intent cropIntent;</p></blockquote>
<p>变量名应简短且易于描述,选用规则尽量简单,易于记忆和联想。<br>尽量避免单个字符的变量名,除非是用于一次性的临时变量,临时的整形变量一般命名为<strong> i,j,k,m,n</strong>。字符型的变量一般使用<strong>c,d,e</strong>。<br>对于<code>View</code>变量的命名规则,如果<code>View</code>是一个单词的,采用第一个单词小写的方式+对应View的描述进行,例如:</p>
<blockquote><p>private View viewUserInfo;</p></blockquote>
<p>如果是两个单词组成的View,比如:<code>TextView</code>,一般采用缩写的方式,例如:</p>
<blockquote><p>private TextView tvUserName;</p></blockquote>
<p>一般情况下<code>Button</code>缩写为:<strong>btn</strong>。</p>
<ul><li><h4>静态变量</h4></li></ul>
<p>为了可以很方便的区分静态变量,静态变量的命名一般采用小写的s开头,后面单词的命名规则和<code>成员变量</code>保持一致,例如:</p>
<blockquote><p>private static Map<String, String> sCacheStrings;</p></blockquote>
<ul><li><h4>常量</h4></li></ul>
<p>常量命名规则一般是所有的单词都是大写,中间使用_(下划线)分割,例如:</p>
<blockquote><p>private static final float SCALE_RATE = 1.25f;</p></blockquote>
<p>代码中不允许出现单独的字符串或数字常量,比如<code>xx.equals("1")</code>,单独的字符串或数字不利于理解和后期的维护。如果需要使用数据或字符,请按照他们的含义封装成静态常量,或者使用枚举,for语句除外。</p>
<h2>方法</h2>
<p>方法命名规则采用小驼峰命名法例如:onCreate(),onRun(),方法名一般采用动词或者动名词。<br>一般使用的方法名前缀。</p>
<ul>
<li><p>getXX()返回某个值的方法</p></li>
<li><p>initXX() 初始化相关方法,比如初始化布局:initView()</p></li>
<li><p>checkXX()和isXX() 方法为boolean值的时候使用is或者check为前缀</p></li>
<li><p>saveXX() 保存数据</p></li>
<li><p>clearXX()和removeXX() 清除数据</p></li>
<li><p>updateXX() 更新数据</p></li>
<li><p>processXX() 对数据进行处理</p></li>
<li><p>dispalyXX() 显示某某信息</p></li>
<li><p>drawXX() 绘制数据或者效果</p></li>
</ul>
<p>另外对于方法的其他一些规范:</p>
<ul>
<li><p>方法的参数尽可能不超过4个,需要更多的参数的时候可以是使用类的作为方法的参数</p></li>
<li><p>方法参数中尽量少使用boolean,使用boolean传参不利于代码的阅读</p></li>
<li><p>方法尽量不超过15行,方法过长,说明当前方法业务逻辑过于复杂,需要进行方法拆分</p></li>
<li><p>一个方法只做一件事,</p></li>
<li><p>如果一个方法返回的是一个错误码,可以使用异常</p></li>
<li><p>不使用try catch 处理业务逻辑</p></li>
<li><p>尽可能不实用null,替代为异常或者使用空的变量,比如<code>Collections.emptyList()</code></p></li>
</ul>
<h2>Layout</h2>
<p>Layout的命名规则需要和使用他们的组件对应,方便查找和维护,比如我们在创建一个用户信息的<code>UserInfoActivity</code>,对应的Layout的命名就应该是<code>activity_user_info.xml</code>。<br>对应Andorid组件的<code>Layout</code>命名规则:</p>
<ul>
<li><p>Activity -> <code>activity_user_info.xml</code></p></li>
<li><p>Fragment -> <code>fragment_sign_up.xml</code></p></li>
<li><p>Dialog -> <code>dialog_change_password.xml</code></p></li>
<li><p>AdapterView Item -> <code>item_user.xml</code></p></li>
<li><p>Layout文件只是布局文件的一部分 -> <code>partial_stats_bar.xml</code></p></li>
</ul>
<h2>string和color</h2>
<p>项目中使用的string和color的值原则上都是必须放在<code>strings.xml</code>和<code>colors.xml</code>中,不要放在Java代码中,这样的好处是可复用,提高维护性,减少非必要的代码。<br><code>xml</code>的资源命名,字母全部小写,多个单词之间使用_(下划线)分割.<br>比如:</p>
<blockquote><p><string name="app_name">example</string></p></blockquote>
<p>建议color的命名中体现其<code>ARGB</code>值,比如:</p>
<blockquote><p><color name="color_feb749">#feb749</color></p></blockquote>
<p>这样的写法对于代码提示更加的友好,有利于对照标注图查找颜色值。</p>
<h2>id命名</h2>
<p>layout中使用的<code>id</code>的单词要全部小写,单词之间使用下划线分割,使用名词或者名词词组,应该通过id的命名可以直接理解当前的<code>View</code>要实现的功能.<br>例如:</p>
<blockquote><p>@+id/tv_user_name_show</p></blockquote>
<p>id命名的第一个单词使用View的缩写,如果View只是一个单词,缩写就是当前单词。一般<code>Button</code>的缩写为:<strong>btn</strong>。</p>
<h2>Drawable命名</h2>
<p><code>Drawable</code>的命名规则根据使用的控件来命名,控件的缩写在前面,后面使用表示其功能的一个或者多个单词,中间使用使用_下划线分割。比如:</p>
<ul>
<li><p>Action bar使用<code>ab_</code>,比如:<code>ab_stacked.png</code></p></li>
<li><p>Button 使用<code>btn_</code></p></li>
<li><p>Dialgo 使用<code>dialog_</code> </p></li>
<li><p>Divide 使用 <code>divider_</code> </p></li>
<li><p>Icon 使用 <code>ic_</code></p></li>
<li><p>Menu 使用<code>menu_</code></p></li>
<li><p>Notification使用 <code>notification_</code></p></li>
<li><p>Tabs 使用<code>tab_</code></p></li>
</ul>
<p><code>Drawable</code>是有多个状态的,在命名中体现出状态的不同,比如:</p>
<ul>
<li><p>Normal 对应<code>_normal</code>结尾,比如<code>btn_order_normal.9.png</code></p></li>
<li><p>Pressed 对应<code>_pressed</code>结尾</p></li>
<li><p>Focused 对应<code>_focused</code>结尾</p></li>
<li><p>Disabled 对应<code>_disabled</code>结尾</p></li>
<li><p>Selected 对应<code>_selected</code>结尾</p></li>
</ul>
<p>其他资源文件的命名需要遵守Android的规范即可,比如<code>arrays.xml</code>数组文件,<code>dimens.xml</code>分辨的配置,<code>style.xml</code>样式的配置,资源文件的ID命名规则都是字母小写,使用下划线分割的原则。</p>
<p>参考:<br><a href="https://link.segmentfault.com/?enc=GMCVkWSu0sjyb%2BlMiL97jA%3D%3D.1YsuurK99NSHSD1%2FM786U3rx15hIIq2ZxuHrYc9uX0o1YltfB96AAXShDh7B9fumlVDMmpd30m76PauZkc8V4T0kS5%2F%2FdsV1zqxCL8pvrDm62wIlR6ThRBTFasugrmMV" rel="nofollow">https://github.com/ribot/android-guidelines/blob/master/project_and_code_guidelines.md</a> <br><a href="https://link.segmentfault.com/?enc=nyoGVcMt7L27O96gtLzT4Q%3D%3D.8HYLNtDHOVcHwVgOQsvlfn9Z8mumatsUDQzuNXq1ZhHbxQ%2F83r9DU6uMzGOHPUqsZ6pUQ6JFRD1YvBE0P17bbg%3D%3D" rel="nofollow">http://source.android.com/source/code-style.html</a> <br><a href="https://link.segmentfault.com/?enc=9LbCCYfSvqfUXKpFzpMyfQ%3D%3D.WdI5r7DDizqt1ys9CXgN%2B%2FFaTMqdunFwJyL45mfmDAgTOfQQD7SpUIs26uddi%2Bv4vbxj5W5IF%2BVPaqV4gSvpQQ%3D%3D" rel="nofollow">https://google.github.io/styleguide/javaguide.html</a></p>
npm升级所有可更新包
https://segmentfault.com/a/1190000005857342
2016-07-02T16:20:11+08:00
2016-07-02T16:20:11+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
34
<p>使用npm管理node的包,可以使用<code>npm update <name></code>对单个包升级,对于npm的版本大于 <code>2.6.1</code>,可以使用命令:</p>
<blockquote><p>npm install -g</p></blockquote>
<p>升级全局的本地包。</p>
<p>对于版本小于<code>2.6.1</code>的一个一个包的升级实在是太麻烦,就想找到一个升级所有本地包的方法,找到两个比较好的方式:<code>shell脚本</code>和<code>npm-ckeck</code></p>
<h2>shell脚本</h2>
<p>使用shell脚本升级npm包,首先所在找到需要升级的包和版本号,再使用<code>npm install</code>完成升级。<br><code>npm -g</code>是管理本地全局包的命令。通过<code>npm -g outdated</code>可以查看那些包有更新:</p>
<blockquote><p>npm -g outdated </p></blockquote>
<pre><code>Package Current Wanted Latest Location
appium 1.5.2 1.5.3 1.5.3
bower 1.7.0 1.7.9 1.7.9
cordova 5.4.1 6.2.0 6.2.0
eslint 2.13.0 3.0.0 3.0.0
fsevents 1.0.8 1.0.12 1.0.12
grommet 0.4.1 0.6.9 0.6.9
requirejs 2.1.22 2.2.0 2.2.0</code></pre>
<p>这里列出来了,当前版本,和最后的版本,只需要得到所有需要升级的包名和版本号就可以使用<code>npm -g install <name></code>直接升级了。<br><code>npm -g outdated</code>还可以使用目录的方式展示,再从中提取出包名和版本号。</p>
<blockquote><p>npm -g outdated --parseable --depth=0</p></blockquote>
<pre><code>/usr/local/lib/node_modules/appium:appium@1.5.3:appium@1.5.2:appium@1.5.3
...</code></pre>
<p>在通过<code>cut</code>命令就可以得到最后要升级版本号和包名:</p>
<blockquote><p>npm -g outdated --parseable --depth=0 | cut -d: -f2</p></blockquote>
<pre><code>appium@1.5.3
.....</code></pre>
<p>完整的脚本:</p>
<pre><code>#!/bin/sh
set -e
#set -x
for package in $(npm -g outdated --parseable --depth=0 | cut -d: -f2)
do
npm -g install "$package"
done</code></pre>
<p>脚本下载地址:<a href="https://link.segmentfault.com/?enc=7f55F4tVa5XKSUWQKPtgJg%3D%3D.OjG%2FN%2FdiLhL0hvNyO8zdtBH86aAXGj2O%2FOMI0ID9AY8PoMnPo1n5u%2BzrdN93k8Y7Z1Mje6Sk5O78lVveU8Vj1w%3D%3D" rel="nofollow">https://github.com/jjz/script/blob/master/npm-upgrade.sh</a></p>
<h2>npm-check</h2>
<p><a href="https://link.segmentfault.com/?enc=texSEDUQyKdv6eCZjNCHJA%3D%3D.pxwm%2Btfj962j9Buq8bb7eBzzJwh2nFU44tVc2WpOh9RCCogsFkY7vR6pOCjsVX8s" rel="nofollow">npm-check</a>是用来检查npm依赖包是否有更新,错误以及不在使用的,我们也可以使用npm-check进行包的更新。<br>安装npm-check:</p>
<blockquote><p>npm install -g npm-check</p></blockquote>
<p>检查npm包的状态:</p>
<blockquote><p>npm-check -u -g</p></blockquote>
<p><img src="/img/remote/1460000008304944" alt="CA5E1D6E-93B8-40CA-B190-273B87364C8C.png" title="CA5E1D6E-93B8-40CA-B190-273B87364C8C.png"></p>
<p>通过上下键可以移动光标,使用空格键可以选择需要处理的包,回车直接进行处理。<br>选择<code>npm@3.10.2</code>包升级到<code>3.10.3</code>:</p>
<pre><code>? Choose which packages to update. npm@3.10.3
$ npm install --global npm@3.10.3 --color=always
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
/usr/local/lib
└─┬ npm@3.10.3
├── aproba@1.0.4
├── has-unicode@2.0.1
└── read-package-tree@5.1.5
[npm-check] Update complete!
[npm-check] npm@3.10.3
[npm-check] You should re-run your tests to make sure everything works with the updates.</code></pre>
<p>通过以上两种方式可以更便利的管理本地的<code>npm</code>包。</p>
<p>参考:<a href="https://gist.github.com/othiym23/4ac31155da23962afd0e">https://gist.github.com/othiym23/4ac31155da23962afd0e</a></p>
react-native 遇到的错误
https://segmentfault.com/a/1190000005857166
2016-07-02T15:48:14+08:00
2016-07-02T15:48:14+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>记录下在react-native开发中遇到的错误</p>
<h2>ReferenceError: Can't find variable: __fbBatchedBridge...</h2>
<p>react-native启动用来做JavaScript的代码的服务,是本地的服务,App默认访问的host地址是<code>localhost</code>,运行到真实的设备上面的时候无法访问react-native服务,因此会出现上面的错误。</p>
<h5>Android解决方案:</h5>
<ol><li><p>对adb的server设置反向代理</p></li></ol>
<blockquote><p>adb reverse tcp:8081 tcp:8081</p></blockquote>
<ol><li><p>更改App内的服务地址<br> * 摇晃手机</p></li></ol>
<p> * 点击菜单键<br> * 在电脑上面运行命令<br> >adb shell input keyevent 82</p>
<p>在<code>dev setting</code>里面即可设置。<br> </p>
<h5> ios解决方案:</h5>
<p> 在<code>AppDelegate.m</code>中修改<code>jsCodeLocation</code>:<br> <code>`</code><br> .....<br> jsCodeLocation = [NSURL URLWithString:@"http://192.168.31.191:8081/index.ios.bundle?platform=ios&dev=true"];<br> ....<br> <code>`</code></p>
<p> </p>
<h2>react-native start 错误</h2>
<p>错误信息:</p>
<pre><code>ERROR: Unknown option --no-pretty
ERROR: Unknown option --no-pretty
Watchman: watchman--no-pretty get-sockname returned with exit code 1 ERROR: Unknown option --no-pretty
ERROR watchman--no-pretty get-sockname returned with exit code 1 ERROR: Unknown option --no-pretty
Error: watchman--no-pretty get-sockname returned with exit code 1 ERROR: Unknown option --no-pretty
at ChildProcess.<anonymous> (.../fb-watchman/index.js:198:18)
at emitTwo (events.js:106:13)
at ChildProcess.emit (events.js:191:7)
at maybeClose (internal/child_process.js:852:16)
at Socket.<anonymous> (internal/child_process.js:323:11)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at Pipe._handle.close [as _onclose] (net.js:492:12)</code></pre>
<p>解决方法:<br><code>npm</code>下面的包也有watchman,如果安装了先卸载</p>
<blockquote><p>npm r -g watchman </p></blockquote>
<p>重新安装<code>watchman</code>:</p>
<pre><code>brew uninstall watchman
brew link pcre
brew install --HEAD watchman</code></pre>
<p>如果出现下面的提示错误:</p>
<pre><code>Error: The `brew link` step did not complete successfully
brew link --overwrite watchman
......</code></pre>
<p>直接运行命令:</p>
<blockquote><p>brew link --overwrite watchman</p></blockquote>
<p>出现<em>Linking</em>创建的信息:</p>
<pre><code>Linking /usr/local/Cellar/watchman/HEAD-bbd5957... 4 symlinks created</code></pre>
<p>如果出现权限不足的情况,可以运行命令:</p>
<blockquote><p>sudo chown -R <code>whoami</code> /usr/local</p></blockquote>
Android-单元测试
https://segmentfault.com/a/1190000005841437
2016-06-30T16:57:08+08:00
2016-06-30T16:57:08+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>Android的单元测试有两种方式:<code>本地单元测试</code>和<code>设备单元测试</code>,<code>本地单元测试</code>可以直接运行在本地机器上面的<code>Java Virtual Machine(JVM)</code>。它的特点是运行时间短,执行效率高,但是没有<em>Android framework</em>的支持,每个文件都可以进行单独的单元测试。<br>而在Android设备上的单元测试,运行的是一个单独的APK,直接运行到虚拟机或者物理Android设备上,这种单元测试拥有整个App的生命周期和运行环境。<br>这里主要介绍以运行在Android设备上的单元测试为主。</p>
<h2>单元测试文件在哪里?</h2>
<p>首先先看下单元测试文件是什么位置,下面是一个Android项目的基本结构:<br><img src="/img/remote/1460000008996688" alt="Android项目结构" title="Android项目结构"></p>
<p>其中:</p>
<ul>
<li><p><code>../app/src/test/java</code>目录下是<strong>本地单元测试</strong>的文件目录。</p></li>
<li><p><code>../app/src/androidTest/java</code>目录下就是<strong>设备单元测试</strong>的文件目录。</p></li>
</ul>
<p>知道了单元测试文件的目录,下面就看下如何配置单元测试环境。</p>
<h2>配置单元测试环境</h2>
<p>需要使用单元测试,需要引入单元测试的依赖库,在<code>AndroidManifest.xml</code>里面添加<em>uses-library</em>:</p>
<pre><code> <application
.....
<uses-library android:name="android.test.runner"/>
</application></code></pre>
<p>然后再配置单元测试框架的启动项:</p>
<pre><code><manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jjz">
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.jjz"
android:label="test"/>
</manifest></code></pre>
<ul>
<li><p><code>android.test.InstrumentationTestRunner</code>:是Android单元测试其中的一个启动项,用来启动单元测试。</p></li>
<li><p><code>com.android.test.runner.MultiDexTestRunner</code>:是另一个单元测试的启动项用来启动<code>MultiDex</code>的项目。</p></li>
</ul>
<p>这里设置了<code>android:targetPackage</code>与项目的<code>package</code>相同,这样当前单元测试和应用程序会运行在同一个进程里面。<br>环境配置完成之后,就可以编写单元测试的代码了?</p>
<h2>编写单元测试</h2>
<p>为了使用单元测试方便,先在项目中增加一个方法,该方法用来比较两个字符串的是否相等:</p>
<pre><code>public class StringUtil {
public static boolean compare(String source, String other) {
if (source == null) {
return other == null;
} else {
return source.equals(other);
}
}
}</code></pre>
<p>在对方法<code>compare</code>进行单元测试,在<code>../app/src/androidTest/java/com.jjz</code>添加文件<code>StringUtilTest.java</code>,文件内容为:</p>
<pre><code>public class StringUtilTest extends AndroidTestCase {
public void testCompare() {
Assert.assertFalse(StringUtil.compare(null,"123"));
Assert.assertTrue(StringUtil.compare("123","123"));
}
}</code></pre>
<p>需要注意的是类名必须使用<code>Test</code>结尾,并且要继承<code>AndroidTestCase</code>,而单方法必须使用<code>test</code>开头。<br>编写完单元测试之后,就可以运行了。</p>
<h2>运行单元测试</h2>
<p>在<code>../app/androidTest/java</code>文件夹上面,可以看到运行单元测试的按钮,如图所示:<br><img src="/img/remote/1460000008996689" alt="Run all tests" title="Run all tests"><br>直接点击<code>Run all tests </code>,可以看到单元测试的结果:<br><img src="/img/remote/1460000008996690" alt="测试结果" title="测试结果"><br>这样就完成了一个简单的单元测试。</p>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=%2BW7RW4KJblb8e%2F0giRdkdg%3D%3D.aw%2FPWtJPzgJTQ%2F0dJpDPUxJIukRuMjnknDm%2BwguOY8JFN95x%2FSEqy5V3IZcYBv4iHLQFKGJZMWqh2R2%2B9274GA%3D%3D" rel="nofollow">https://github.com/jjz/android/tree/master/experimental</a></p>
Andorid Studio NDK开发-使用NDK库
https://segmentfault.com/a/1190000005766932
2016-06-21T20:05:00+08:00
2016-06-21T20:05:00+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
1
<p><code>C语言</code>是一个巨大的宝库,Android是一个以Linux为基础的开源操作系统,系统底层很多的实现都是基于<code>C语言</code>开发,比如图像处理,加密等。另一方面<code>C语言</code>的运行效率也比Java开发要高很多,因此为了高效率的运行有时候也会使用<code>C语言</code>开发一些功能。再Android上面使用<code>C语言</code>开发就需要使用NDK,在使用NDK开发的过程中会使用大量的库,系统自带的库,第三方库以及自己写的库等。<br>使用<code>Android Studio</code>调用NDK的库是非常简便,NDK内置了一些库方便开发者使用比如:<code>Log库</code>,还有一些比较常用的第三方库比如:<code>OpenSSL</code>。下面会分别介绍下,这两种库的使用。</p>
<h2>调用系统库</h2>
<p><code>Log</code>是在Android开发过程用来调试程序必备的工具之一,他会把日志信息输入到<code>Logcat</code>中,如何在NDK中使用<code>android.util.Log</code>方便在<code>Logcat</code>中查看JNI程序的运行情况呢?<br>这就需要在NDK开发中导入Android系统的<code>Log</code>库。首先需要在<code>gradle</code>中引入Log库,引入的方式使用是在<code>ldLibs</code>中添加:</p>
<pre><code>model{
....
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
ndk {
moduleName "experiment"
ldLibs.addAll([ 'log']);
}
}
}</code></pre>
<p>直接gradle中的<code>ldLIbs</code>中加入log就可以了,如果还需要引入其他的系统库,只要在数组中直接增加即可。<br>下面来测试下<code>log库</code>的使用,先定义一个<code>native</code>的方法:</p>
<blockquote><p>public static native void callLogFromJni();</p></blockquote>
<p>在JNI中调用Log库:</p>
<pre><code>//引入 log
#include <android/log.h>
JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callLogFromJni(JNIEnv *env, jclass type) {
__android_log_print(ANDROID_LOG_INFO,"jni-log","from jni log");
}</code></pre>
<p>第一个参数,<code>ANDROID_LOG_INFO</code>是Log的级别他包含:</p>
<pre><code>typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;</code></pre>
<p>一般我们常用的是</p>
<ul>
<li><p><code>ADNROID_LOG_VERBOSE</code>->Log.v</p></li>
<li><p><code>ANDROID_LOG_DEBUG</code>->Log.d</p></li>
<li><p><code>ANDROID_LOG_INFO</code>-> Log.i</p></li>
<li><p><code>ANDROID_LOG_WARN</code>->Log.w</p></li>
<li><p><code>ANDROID_LOG_ERROR</code>->Log.e</p></li>
</ul>
<p>第二个参数是tag,用来方便的对Log分类。<br>第三个参数是message,对应Log的具体信息。</p>
<p>一般会采用宏定义的方式,定义Log的输出的方法,方便调用,例如:</p>
<pre><code>#define LOG_TAG "jni-log"
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
</code></pre>
<p>这里定义了一个<code>warning log</code>的宏,在代码里面可以直接调用:</p>
<pre><code> LOGW("log from define");</code></pre>
<p>系统库的调用比较简单方便,使用第三方库就比较麻烦些,第三方库需要使用NDK重新编译才能在JNI中调用。</p>
<h2>调用第三方类库</h2>
<p><code>OpenSSL</code>是最常用的加密库,下面就以<code>OpenSSL</code>为例,介绍下在<code>gradle-experimental</code>中如何引入第三方类库。<br>关于如何编译<code>Android</code>下的<code>OpenSSL</code>详见:<a href="https://link.segmentfault.com/?enc=qYyOCWX6CQQc%2F9W%2FvXrrjQ%3D%3D.OzIewrSRBnUFXdOoEkVIEO%2Fe6LDQEacQfg%2FFN3YmSzXYVCfHSvzvqNzjB2pkBjsL" rel="nofollow">Andorid Studio NDK 开发 - 编译 OpenSSL 类库</a>。<br>首先定义对于库的<code>repositories</code>,用来指定库的基本信息,包括库文件的路径,头文件的路径以及链接的方式等,详见如下代码:</p>
<pre><code>model {
repositories{
libs(PrebuiltLibraries) {
// Configure one pre-built lib: static
openssl {
// 头文件地址
headers.srcDir "/usr/local/ssl/android-23/include"
// 静态链接库的引用,
binaries.withType(StaticLibraryBinary) {
staticLibraryFile = file("libs/libcrypto.a")
}
//动态链接库的引用
// binaries.withType(SharedLibraryBinary) {
// sharedLibraryFile = file("libs/libcrypto.so")
// }
}
}
}
}</code></pre>
<p>c语言的类库分为<code>静态链接库(.a)</code>和<code>动态链接库(.so)</code>,静态类库和动态类库在引入方式上是不一样的,分为对应:</p>
<ul>
<li><p><code>StaticLibraryBinary</code>->静态库</p></li>
<li><p><code>SharedLibraryBinary</code>-> 动态链接库</p></li>
</ul>
<p>这里引入的库为静态链接库,库的repositories名称为:<code>openssl</code>.<br>定义好了一个<code>repositories</code>,现在就需要调用了,在<code>gradle</code>可以指定库的依赖:</p>
<pre><code>model{
......
android{
.....
sources {
main {
jni {
dependencies{
//静态链接库
library 'openssl' linkage 'static'
//动态链接库
// library 'openssl' linkage 'shared'
}
source {
srcDir "src/main/jni"
}
}
jniLibs{
source{
srcDir "libs/"
}
}
....
}
}
}
}</code></pre>
<p>在<code>model.android.sources.main</code>中指定库的依赖,依赖的是上面定义的<code>openssl</code>,<code>linkage</code>类型为<strong>static</strong>,如果是动态链接库则<code>linkage</code>就是<strong>shared</strong>。<br>因为在编译<code>OpenSSL</code>设置了只支持<code>arm</code>结构的cpu,所以还需要指定<code>abi</code>为对应为arm架构,在<code>model.android</code>添加配置:</p>
<pre><code> ndk {
moduleName "experiment"
abiFilters.addAll(['armeabi', 'armeabi-v7a'])
}</code></pre>
<p>定义了库的链接,就可以在代码中测试下<code>OpenSSL</code>的使用了。<br>首先定义一个<code>native</code>方法,该方法的目的是从<code>OpenSSL</code>中读取随机数:</p>
<pre><code>public static native byte[] getRandom();</code></pre>
<p>对应的<code>JNI</code>方法:</p>
<pre><code>//引入OpenSSL的rand
#include <openssl/rand.h>
JNIEXPORT jbyteArray JNICALL
Java_com_jjz_NativeUtil_getRandom(JNIEnv *env, jclass type) {
unsigned char rand_str[128];
//使用OpenSSL的方法
RAND_seed(rand_str, 32);
jbyteArray bytes = (*env)->NewByteArray(env, 128);
(*env)->SetByteArrayRegion(env, bytes, 0, 128, rand_str);
return bytes;
}</code></pre>
<p><code>RAND_seed</code>是OpenSSL的方法,能够读取随机数。这段代码的意思就是读取一个<code>128位的随机数</code>,然后转换为Java的<code>byte数组</code>。<br>在界面上面使用实现出读取的随机数内容:</p>
<pre><code>tv2.setText(Base64.encodeToString(NativeUtil.getRandom(), Base64.DEFAULT));</code></pre>
<p>运行之后可以在界面看到一段随机的字符串显示:</p>
<p><img src="/img/remote/1460000008899549" alt="从openssl中读取随机数" title="从openssl中读取随机数"><br>可以看到使用<code>gradle-experiment</code>无论是调用系统库还是第三方库都比较简单。<br>以上源代码地址:<a href="https://link.segmentfault.com/?enc=pwtaLn5GwHcbYSVSjpkMrw%3D%3D.VWDtTj6UZOUfitN%2BuNX5q4EtQZiV5sTIF5kFzLQcr1UQkGqvUnC3J%2Fy4rZZYqKB5FmFO7DS46mYbIrZiygTTKw%3D%3D" rel="nofollow">https://github.com/jjz/android/tree/master/UseLibrary</a></p>
Android Studio NDK开发-JNI调用Java函数
https://segmentfault.com/a/1190000005760699
2016-06-20T18:16:59+08:00
2016-06-20T18:16:59+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
2
<p>相对于NDK来说SDK里面有更多API可以调用,有时候我们在做NDK开发的时候,需要在<code>JNI</code>直接Java中的方法和变量,比如<code>callback</code>,系统信息等....<br>如何在<code>JNI</code>中调用Java方法呢?就需要先了解<code>FindClass</code>和<code>GetMethodID</code>了。</p>
<h2>FindClass和GetMethodID</h2>
<p>在JNI中可以通过<code>FindClass</code>可以找到Java类,得到<code>jclass</code>,例如:</p>
<blockquote><p>jclass clz=(*env)->FindClass(env,"com/jjz/JniHandle");</p></blockquote>
<p><code>FindClass</code>的第二个参数需要传入类的完整包名。</p>
<p>使用<code>GetMethodID</code>可以获取类的方法,得到jmethodID,例如:</p>
<blockquote><p>jmethodID getStringFromJava=(*env)->GetMethodID(env,class,"getStringForJava","()V");</p></blockquote>
<p>如果调用的是静态方法需要使用<code>GetStaticMethodID</code>获取。通过<code>FindeClass</code>可以在JNI中找到需要调用的类,<code>GetMethodID</code>可以找到对应的方法,这样就可以在JNI中调用Java的方法了。<br>在GetMethodID中,第四个参数是<code>()V</code>,这个是方法签名。那么方法签名的规则又是怎么样呢?</p>
<h2>方法签名</h2>
<p>在<code>GetMethodID</code>中第四个参数<code>()V</code>就是方法签名,Java是支持重载的,所以需要标明方法的传参和返回值,这就是方法的签名。它是用来保证方法的唯一性。其中<code>()</code>代表不传参数,<code>V</code>代表返回值为<strong>void</strong>。<br>方法签名对于Java的类型都有一一对应的值。方法签名中用大写的字母对应了java的基本数据类型:</p>
<ul>
<li><p>Z -> boolean</p></li>
<li><p>B -> byte</p></li>
<li><p>C -> char</p></li>
<li><p>S -> short</p></li>
<li><p>I -> int</p></li>
<li><p>J -> long</p></li>
<li><p>F -> float</p></li>
<li><p>D -> double</p></li>
</ul>
<p>其实就是有两个比较特殊的:<code>boolean</code>对应的是Z,<code>long</code>对应的J,其他的都是首个字母的大写即可。</p>
<p>数组的表示方法,以<code>[</code>为标志,一个<code>[</code>标识一维数组,<code>[[</code>表示二维数组,例如:</p>
<ul>
<li><p>byte[] -> [B</p></li>
<li><p>int[][] -> [[I</p></li>
</ul>
<p>引用类型的表示方法,需要以<code>L</code>开头,以<code>;</code>结束,中间对应类型的包名加类名,例如:</p>
<ul>
<li><p>String -> Ljava/lang/String;</p></li>
<li><p>Object -> Ljava/lang/Object;</p></li>
</ul>
<p>自定义类的表示方法,比如包名为<em>jjz.example</em>,类名为<em>JniHandle</em>的表示方法:</p>
<ul><li><p>jjz.example.JniHandle ->Ljjz/example/JniHandle;</p></li></ul>
<p>除了手动输入类名和方法签名以外,JDK还提供了直接生成方法签名的工具<code>javap</code>。<br>在<code>build</code>之后可以在路径<code>../app/build/intermediates/classes/debug</code>下可以找到build之后生成的<code>.class</code>文件,运行命令:</p>
<blockquote><p>javap -s com/jjz/JniHandle</p></blockquote>
<p>就可以得到这个类的所有的方法签名:</p>
<pre><code>Compiled from "JniHandle.java"
public class com.jjz.JniHandle {
public com.jjz.JniHandle();
descriptor: ()V
public static java.lang.String getStringFromStatic();
descriptor: ()Ljava/lang/String;
public java.lang.String getStringForJava();
descriptor: ()Ljava/lang/String;
}</code></pre>
<p>有了类的引用,和方法的签名就可以直接在<code>JNI</code>中调用Java方法了,下面分别介绍下静态方法和类方法的调用。</p>
<h2>静态方法的调用</h2>
<p>调用类的静态方法,首先要得到类的引用,再调用类的静态方法。<br>先定义一个类和静态方法用来提供给<code>JNI</code>调用:</p>
<pre><code>public class JniHandle {
public static String getStringFromStatic() {
return "string from static method in java";
}
}</code></pre>
<p>在定义了一个<code>native</code>方法<code>com.jjz.NativeUtil.callJavaStaticMethodFromJni</code>,生成这个方法的<code>JNI</code>代码,在<code>JNI</code>代码中调用<em>JniHandle</em>类的静态方法:</p>
<pre><code>JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callJavaStaticMethodFromJni(JNIEnv *env, jclass type) {
jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle");
if (NULL == jniHandle) {
LOGW("can't find JniHandle");
return;
}
jmethodID getStringFromStatic = (*env)->GetStaticMethodID(env, jniHandle, "getStringFromStatic",
"()Ljava/lang/String;");
if (NULL == getStringFromStatic) {
(*env)->DeleteLocalRef(env, jniHandle);
LOGW("can't find method getStringFromStatic from JniHandle ");
return;
}
jstring result = (*env)->CallStaticObjectMethod(env, jniHandle, getStringFromStatic);
const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
(*env)->DeleteLocalRef(env, jniHandle);
(*env)->DeleteLocalRef(env, result);
LOGW(resultChar);
}</code></pre>
<p>在Java中调用<code>com.jjz.NativeUtil.callJavaStaticMethodFromJni</code>可以该方法可以在logcat中看到<code>string from static method in java</code>,这样就完成了在<code>JNI</code>中调用了<code>Java</code>静态方法。</p>
<h2>类方法的调用</h2>
<p>调用类方法要更加的复杂一些,调用步骤:</p>
<ol>
<li><p>通过findClass找到类</p></li>
<li><p>通过GetMethodID得到构造函数</p></li>
<li><p>通过调用构造函数得到一个类的实例</p></li>
<li><p>通过GetMethodID得到需要调用的方法</p></li>
<li><p>使用类的实例调用方法</p></li>
</ol>
<p>先定义一个类方法:</p>
<pre><code>public class JniHandle {
public String getStringForJava() {
return "string from method in java";
}
}</code></pre>
<p>再定义一个<code>native</code>方法:<code>com.jjz.NativeUtil.callJavaMethodFromJni</code>,生成该方法的<code>JNI</code>代码,在<code>JMI</code>代码中实现调用<code>JniHandle</code>的类方法<strong>getStringForJava</strong>,代码如下:</p>
<pre><code>
JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callJavaMethodFromJni(JNIEnv *env, jclass type) {
jclass jniHandle = (*env)->FindClass(env, "com/jjz/JniHandle");
if (NULL == jniHandle) {
LOGW("can't find jniHandle");
return;
}
jmethodID constructor = (*env)->GetMethodID(env, jniHandle, "<init>", "()V");
if (NULL == constructor) {
LOGW("can't constructor JniHandle");
return;
}
jobject jniHandleObject = (*env)->NewObject(env, jniHandle, constructor);
if (NULL == jniHandleObject) {
LOGW("can't new JniHandle");
return;
}
jmethodID getStringForJava = (*env)->GetMethodID(env, jniHandle, "getStringForJava",
"()Ljava/lang/String;");
if (NULL == getStringForJava) {
LOGW("can't find method of getStringForJava");
(*env)->DeleteLocalRef(env, jniHandle);
(*env)->DeleteLocalRef(env, jniHandleObject);
return;
}
jstring result = (*env)->CallObjectMethod(env, jniHandleObject, getStringForJava);
const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
(*env)->DeleteLocalRef(env, jniHandle);
(*env)->DeleteLocalRef(env, jniHandleObject);
(*env)->DeleteLocalRef(env, result);
LOGW(resultChar);
}</code></pre>
<p>调用方法<code>com.jjz.NativeUtil.callJavaMethodFromJni</code>即可看到Java中的字符串传递给了<code>JNI</code>最后输出到了<strong>Logcat</strong>上。</p>
<p>在上面的代码中有一个方法叫做<code>DeleteLocalRef</code>,它的意思是释放局部引用,<code>Android VM</code>释放局部引用有两种方法:</p>
<ul>
<li><p>本地方法执行完毕之后<code>VM</code>自动释放</p></li>
<li><p>通过调用<code>DeleteLocalRef</code>手动释放</p></li>
</ul>
<p>既然上面说了<code>VM</code>会自动释放引用为什么还需要手动释放呢?<br>其实某些局部变量会阻止它所引用的对象被GC回收,它所引用的对象无法被GC回收,自己本身也就无法被自动释放,因此需要使用<code>DeleteLocalRef</code>。而这里使用了JNI Local Reference,在JNI中引用了Java对象,如果不使用<code>DeleteLocalRef</code>释放的话,引用无法回收,就会造成内存泄露。</p>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=%2F2ysNnjVF9UBKvBZ9p80wg%3D%3D.l%2BBqDBLRZysz6Dj%2FGn%2FZf7ln8yi5wsMkkQui06yAiztFrwLDwcOy7D7B6nwta72VINgSgrXCyNh0WsZdi4PHZQ%3D%3D" rel="nofollow">https://github.com/jjz/androi...</a></p>
Andorid Studio NDK开发-使用库
https://segmentfault.com/a/1190000005752063
2016-06-19T12:11:47+08:00
2016-06-19T12:11:47+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p>C语言是一个巨大的宝库,系统底层的很多的实现都是基于C语言实现的,比如图像处理,加密等。<br>C语言的运行效率也是很高的,因此为了效率有时候也会引入第三方的C语言库。<br>总而言之,会在NDK开发的过程中会使用大量的库,系统自带的库,第三方库等。<br>在<code>gradle-experimental</code>中使用C语言的库是非常便利的。</p>
<h2>调用系统库</h2>
<p>Log是在Android开发过程用来调试程序必备的工具之一,如何在NDK中使用<code>android.util.Log</code>方便在Logcat中查看JNI程序的运行情况呢?这就需要在NDK中导入Android系统的<code>Log</code>库。<br>首先需要在在<code>gradle</code>中引入Log库:</p>
<pre><code>model{
....
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
ndk {
moduleName "experiment"
ldLibs.addAll([ 'log']);
}
}
}</code></pre>
<p>直接在ldLIbs中加入log就可以,如果需要引入其他的系统库,只要在数组中直接增加即可。<br>再定义一个native的方法:</p>
<blockquote><p>public static native void callLogFromJni();</p></blockquote>
<p>在Jni中调用Log库的方法:</p>
<pre><code>//引入 log
#include <android/log.h>
JNIEXPORT void JNICALL
Java_com_jjz_NativeUtil_callLogFromJni(JNIEnv *env, jclass type) {
__android_log_print(ANDROID_LOG_INFO,"jni-log","from jni log");
}</code></pre>
<p>第一个参数,<code>ANDROID_LOG_INFO</code>是log的级别他包含:</p>
<pre><code>typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;
</code></pre>
<p>一般我们常用的是<code>ADNROID_LOG_VERBOSE</code>,<code>ANDROID_LOG_DEBUG</code>,<code>ANDROID_LOG_INFO</code>,<code>ANDROID_LOG_WARN</code>,<code>ANDROID_LOG_ERROR</code>分别对应java中的<code>Log.v</code>,<code>Log.d</code>,<code>Log.i</code>,<code>Log.w</code>,<code>Log.e</code>。<br>第二个参数是tag,用来方便的对log分类。<br>第三个参数是message,对应log的具体信息。</p>
<p>一般还会采用宏定义的方式,定义Log的输出的方法,方便调用:</p>
<pre><code>#define LOG_TAG "jni-log"
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
</code></pre>
<p>这里定义了一个warning log的宏,在代码里面可以直接调用:</p>
<pre><code> LOGW("log from define");</code></pre>
<h2>使用第三方类库</h2>
<p>OpenSSL是最常用的加密库之一,下面以OpenSSL为例,介绍下在<code>gradle-experimental</code>中如何引入第三方类库。关于如何编译Android下的OpenSSL详见:<a href="https://link.segmentfault.com/?enc=vAUnafGpnJN%2F8Je8xNhHiA%3D%3D.2yhyKYsPEDNhkJJBDiu%2FjgL4%2FWw0y6RcPeyBny6yFHbiyXTs7HW4qTTynEYQBviK" rel="nofollow">编译Android的OpenSSL类库</a>。<br>首先定义对于库的<code>repositories</code>:</p>
<pre><code>model {
repositories{
libs(PrebuiltLibraries) {
// Configure one pre-built lib: static
openssl {
// 头文件地址
headers.srcDir "/usr/local/ssl/android-23/include"
// 静态链接库的引用,
binaries.withType(StaticLibraryBinary) {
staticLibraryFile = file("libs/libcrypto.a")
}
//动态链接库的引用
// binaries.withType(SharedLibraryBinary) {
// sharedLibraryFile = file("libs/libcrypto.so")
// }
}
}
}
}</code></pre>
<p>c语言的类库分为静态链接库(.a)和动态链接库(.so),静态类库和动态类库的引入方式是不一样的,分为对应:<code>StaticLibraryBinary</code>和<code>SharedLibraryBinary</code>。这里引入的库为静态链接库,名称为:<code>openssl</code>.</p>
<p>指定库依赖:</p>
<pre><code>model{
......
android{
.....
sources {
main {
jni {
dependencies{
library 'openssl' linkage 'static'
//动态链接库
// library 'openssl' linkage 'shared'
}
source {
srcDir "src/main/jni"
}
}
jniLibs{
source{
srcDir "libs/"
}
}
java{
source{
srcDir "src/main/java"
}
}
}
}
}
}</code></pre>
<p>这里在model.android.sources.main中指定库的依赖为上面定义的<code>openssl</code>,<code>linkage</code>类型为static,如果是动态链接库<code>linkage</code>就是shared。<br>因为编译的OpenSSL只支持arm结构的cpu,因此需要指定abi为对应的cpu,在model.android添加配置:</p>
<pre><code> ndk {
moduleName "experiment"
abiFilters.addAll(['armeabi', 'armeabi-v7a'])
}</code></pre>
<h2>使用OpenSSL</h2>
<p>首先定义一个<code>native</code>方法,需要从OpenSSL中读取随机数:</p>
<pre><code>public static native byte[] getRandom();</code></pre>
<p>生成对应的JNI方法:</p>
<pre><code>//引入OpenSSL的rand
#include <openssl/rand.h>
JNIEXPORT jbyteArray JNICALL
Java_com_jjz_NativeUtil_getRandom(JNIEnv *env, jclass type) {
unsigned char rand_str[128];
RAND_seed(rand_str, 32);
jbyteArray bytes = (*env)->NewByteArray(env, 128);
(*env)->SetByteArrayRegion(env, bytes, 0, 128, rand_str);
return bytes;
}</code></pre>
<p><code>RAND_seed</code>是OpenSSL的方法,读取随机数。这段代码就是读取一个128的随机数,然后转换为java的byte[]。</p>
<p>在界面上面使用读取随机数的方法:</p>
<pre><code>tv2.setText(Base64.encodeToString(NativeUtil.getRandom(), Base64.DEFAULT));</code></pre>
<p>运行之后可以在界面看到一段随机的字符串显示:</p>
<p><img src="http://upload-images.jianshu.io/upload_images/22188-555b303770d83872.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="从openssl中读取随机数" title="从openssl中读取随机数"></p>
<p>源代码地址:<a href="https://link.segmentfault.com/?enc=6wRDH5Amn81dywwGZMqyNA%3D%3D.Vg1g3lodthnXtdx%2B7ACZmpxpPPRT6wq7U7%2FIGeQS3AKbzhQ3nC7a9%2BsfGdBMPI7pCbGCq5fWqR59uEqPFVmBPw%3D%3D" rel="nofollow">https://github.com/jjz/android/tree/master/experimental</a></p>
Andorid Studio NDK开发-编译OpenSSL类库
https://segmentfault.com/a/1190000005747285
2016-06-18T08:17:16+08:00
2016-06-18T08:17:16+08:00
姜家志
https://segmentfault.com/u/jiangjiazhi
0
<p><code>OpenSSL</code>是一个强大的开源安全套接字层密码库,它包含了主要的密码学算法,常用的密钥和证书封装管理以及SSL协议,并提供丰富的应用程序供测试或其他目的使用。<br>在<code>Android</code>上开发对于安全的需求越来越高,虽然<code>OpenSSL</code>出现过几次漏洞,但它仍然是在安全方面的使用最多的加密库之一。<br><code>OpenSSL</code>是一个基于c语言开发的,古老的,开源的加密库,想要在<code>Android</code>上使用<code>OpenSSL</code>必须要借助<code>NDK</code>,先使用NDK编译成Android上面的动态连接库(或者静态链接库),再借助JNI层的封装,提供给Java层调用。<br>这篇文章主要写的是如何编译<code>Android</code>的<code>OpenSSL</code>类库。参考OpenSSL的官方文档:<a href="https://link.segmentfault.com/?enc=L9kx2GwZCKnz%2BMDlu6b2Dg%3D%3D.2NjgsiV3c9lt%2FERnOQp3qW3FZQ7FMCmZvne1oRBN%2BUmnUreF5ygYiiiCu8%2Foa7GJ" rel="nofollow">https://wiki.openssl.org/index.php/Android</a></p>
<h2>前期准备</h2>
<p>环境准备:</p>
<ul>
<li><p>编译环境为MacOS</p></li>
<li><p>OpenSSL的源代码</p></li>
<li><p>Setenv-android.sh 构建脚本</p></li>
<li><p>安装Make</p></li>
<li><p>安装makedepend</p></li>
</ul>
<p><code>OpenSSL</code>可以在github上找到源代码,源代码地址:<a href="https://link.segmentfault.com/?enc=ycpLB8F1tVT2OdcgmD8JMA%3D%3D.s1oQuV84%2FTKNbAcVyD0EKMMDwLsefnSZv6dpsdLkWCutqPwJ4YrbQJl6AtXWzLyA" rel="nofollow">https://github.com/openssl/openssl</a></p>
<blockquote><p>git clone git@github.com:openssl/openssl.git</p></blockquote>
<p>也可以在官网上下载最新的release版本:<a href="https://link.segmentfault.com/?enc=nNlvk%2Bevj3vAQVAaYMZNwQ%3D%3D.WOblKXEQHVJIeY6HmEGZ54geiq4WaHWLElBFk%2FdmqHc%3D" rel="nofollow">https://www.openssl.org/source/</a><br>由于<code>OpenSSL</code>项目的主干(master)上提交的是开发分支,最好把<code>OpenSSL</code>切换到最新的release版本上面</p>
<blockquote><p>git checkout OpenSSL_1_1_0e</p></blockquote>
<p><code>Setenv-android.sh</code>是用来编译Android上的OpenSSL的脚本,下载地址:<a href="https://link.segmentfault.com/?enc=S4r1BWANApX%2Bp%2FogRDwa2Q%3D%3D.QmjC43TEoN0SBvEpVWcX58eRJRahdLP52%2Ffqp4%2BjmG2zdO36jGqU8s6ro5%2BizITNBtt5CW1wEwoqv2%2FhX7eoAA%3D%3D" rel="nofollow">https://wiki.openssl.org/images/7/70/Setenv-android.sh</a></p>
<blockquote><p>wget <a href="https://link.segmentfault.com/?enc=BsY%2F8kh8nL3L6asLlvw3fA%3D%3D.vvJb2XlI0dvdBxIUScdpxCjHdcesy5Vw3kRjmCouMK%2Bw2dIuHa9t5%2F7pHUZtlK9CX8UMRm8HZtOd3A3LhOqmag%3D%3D" rel="nofollow">https://wiki.openssl.org/imag...</a></p></blockquote>
<p>给<code>Setenv-android.sh</code>脚本可以运行的权限</p>
<blockquote><p>chmod a+x Setenv-android.sh</p></blockquote>
<p>安装<code>makedepend</code></p>
<blockquote><p>brew install makedepend</p></blockquote>
<h2>运行脚本</h2>
<p><code>Setenv-android.sh</code>脚本的作用是用来给编译OpenSSL配置Android编译的环境变量的。脚本下载完成之后是不能直接运行,原因在于脚本里面的变量并没有配置,需要配置变量:</p>
<pre><code>ANDROID_NDK_ROOT:
ANDROID_ARCH: arch-arm
ANDROID_EABI: arm-linux-androideabi-4.9
ANDROID_API: android-23
ANDROID_SYSROOT: /platforms/android-23/arch-arm
ANDROID_TOOLCHAIN:
FIPS_SIG:
CROSS_COMPILE: arm-linux-androideabi-
ANDROID_DEV: /platforms/android-23/arch-arm/usr</code></pre>
<p>已经设置了<code>Android NDK</code>和<code>Android SDK</code>变量的,只需要把正确的值配置到脚本就可以了,设置脚本中的变量:</p>
<pre><code>ANDROID_NDK_ROOT=$NDK_HOME
_ANDROID_API="android-23"
_ANDROID_EABI="arm-linux-androideabi-4.9"</code></pre>
<p>运行脚本:</p>
<blockquote><p>./Setenv-android.sh</p></blockquote>
<p>没有<code>Error</code>出现就表示配置正确了。</p>
<h2>编译OpenSSL</h2>
<p>上面的配置已经给OpenSSl的编译环境设置了环境变量例如:</p>
<pre><code>export MACHINE=armv7
export RELEASE=2.6.37
export SYSTEM=android
export ARCH=arm</code></pre>
<p>根据上面的设置的环境变量,再运行<code>./config</code>就可以实现编译Android上OpenSSL的配置,make就可以开始编译了。<br>因为在Android设备上面运行,建议不要编译完整的OpenSSL库,官方给的建议编译Android的选项:</p>
<blockquote><p>shared,no-ssl2,no-ssl3,no-comp,no-hw,no-engine</p></blockquote>
<p>编辑<code>OpenSSl</code>的类库可以安装到本地,这样可以像使用NDK中其他库一样的使用OpenSSL,编译命令可以设置<code>--openssldir</code>用来指定OpenSSL的安装目录。</p>
<blockquote><p>cd openssl-1.0.1t</p></blockquote>
<p>perl -pi -e 's/install: all install_docs install_sw/install: install_docs install_sw/g' Makefile.org<br>./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/usr/local/ssl/$ANDROID_API</p>
<p>运行到这里的时候得到一个这样的信息:</p>
<pre><code>perating system: i686-apple-darwinDarwin Kernel Version 15.5.0: Tue Apr 19 18:36:36 PDT 2016; root:xnu-3248.50.21~8/RELEASE_X86_64
WARNING! If you wish to build 64-bit library, then you have to
invoke './Configure darwin64-x86_64-cc' *manually*.
You have about 5 seconds to press Ctrl-C to abort.</code></pre>
<p>编译的<code>OpenSSL</code>使用的是本地的<code>darwin64-x86_64-cc</code>,并不是想要的<code>arm-linux-androideabi-gcc</code>编译的,编译的这个类库是不能在Android上面使用的。原因是什么呢?查看了下环境变量:</p>
<blockquote><p>echo $ANDROID_API</p></blockquote>
<p>发现这个变量没有配置,上面的环境变量的配置<code>Setenv-android.sh</code>在设置的环境变量并没有起作用。</p>
<h2>优化编译脚本</h2>
<p>在<code>Setenv-android.sh</code>中,尝试下打印<code>$ANROID_API</code>的值,打印<code>$ANDROID_API</code>的内容:</p>
<blockquote><p>echo "ANDROID_API: <code>echo $ANDROID_API</code>"</p></blockquote>
<p>得到结果为:</p>
<pre><code>...
ANDROID_API: android-23
...</code></pre>
<p>可以看到<code>$ANDROID_API</code>变量在<code>Setenv-android.sh</code>的生命周期内是有效的,而脚本运行结束之后设置的变量没有设置成功这个应该是因为我使用了<code>fish</code>有关,因此我就考虑把编译的命令都放在<code>Setenv-adnroid.sh</code>里,在<code>Setenv-android.sh</code>的生命周期内运行完所有的编译命令。<br>重新建一个脚本名字为<code>build-android-openssl.sh</code><br>复制配置好的<code>Setenv-android.sh</code>的内容到<code>build-android-openssl.sh</code>,再添加命令:</p>
<pre><code>cd openssl
make clean
perl -pi -e 's/install: all install_docs install_sw/install: install_docs install_sw/g' Makefile.org
./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine --openssldir=/usr/local/ssl/$ANDROID_API
make depend
make all
sudo -E make install CC=$ANDROID_TOOLCHAIN/arm-linux-androideabi-gcc RANLIB=$ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib</code></pre>
<p>注:该脚本和<code>openssl</code>源码的目录是同一级目录。<br>完成编译之后可以看到<code>/usr/local/ssl/android-23</code>有生成了对应的库和文件,OpenSSL源代码目录下生成了:<code>libcrypto.so</code>和<code>libcrypto.a</code><br>这样就完成了Android的<code>OpenSSL</code>库编译完成,下一步就可以尝试在NDK中引用OpenSSL了。</p>
<p>优化后的脚本地址:<a href="https://link.segmentfault.com/?enc=%2FHMZZcML0VOhzf4U%2F6YbyA%3D%3D.Q5dqb2UMEUIKur6iJtUUQ3XTf6PN9A6MesWz%2BscJvPVXPIqrx10TnXLJXu5rR72lij9n%2BQ%2BCNy%2Bp1SHOTACUx47FEDrm3IZDZpkGiVxYnJ8%3D" rel="nofollow">https://github.com/jjz/script/blob/master/build_android_openssl.sh</a></p>