<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>offbye</title>
  <subtitle>关注移动架构，Android，HTML5，iOS技术前沿</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://offbye.com/"/>
  <updated>2018-07-18T15:28:04.019Z</updated>
  <id>http://offbye.com/</id>
  
  <author>
    <name>offbye</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>程序员如何转型人工智能（机器学习)</title>
    <link href="http://offbye.com/2018/07/18/%E7%A8%8B%E5%BA%8F%E5%91%98%E5%A6%82%E4%BD%95%E8%BD%AC%E5%9E%8B%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%EF%BC%88%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
    <id>http://offbye.com/2018/07/18/程序员如何转型人工智能（机器学习/</id>
    <published>2018-07-18T15:25:03.000Z</published>
    <updated>2018-07-18T15:28:04.019Z</updated>
    
    <content type="html"><![CDATA[<p><strong>本文是为了准备<a href="http://njsd-china.org/IAS2017/" target="_blank" rel="external">IAS2017</a>互联网架构峰会而作，我将主持本次大会中《如何转型为一个人工智能工程师？》圆桌论坛的讨论，欢迎大家围观。</strong></p>
<p>我是一个工作时间比较久的全栈工程师，做过web开发，前端，后端，移动端，HTML5的开发。2016年底开始学习机器学习，做深度学习大概不到半年。目前在研究手机端侧人工智能。在人工智能技术方面我肯定没有研究人工智能很多年的人有经验，但在怎样转型人工智能方面我还是有些体会的。<br><a id="more"></a><br> <img src="http://upload-images.jianshu.io/upload_images/1387855-bdbe475c1fc263ba.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="一个很小的卷积神经网络的结构图"></p>
<h2 id="学习人工智能为什么比学习其他的编程技术要困难"><a href="#学习人工智能为什么比学习其他的编程技术要困难" class="headerlink" title="学习人工智能为什么比学习其他的编程技术要困难"></a>学习人工智能为什么比学习其他的编程技术要困难</h2><ul>
<li><p>首先是人工智能整个领域特别大<br>我们常说的深度学习是机器学习的一个小分支，而机器学习又只是人工智能的一个小分支。但目前深度学习比较热，因此很多人就把深度学习当成人工智能了。具体到业务领域，就有计算机视觉，语音识别，自然语言处理等，每一个领域都很专业，会用到机器学习的各种方法。后面的内容主要是说深度学习和机器学习。</p>
</li>
<li><p>其次人工智能是不确定性的<br>思维模式和传统编程不一样,我们平常接触的编程语言无论前端，后端还是移动端,都是确定性的,确定性是什么意思呢？例如你要修改个传统系统的bug，你弄明白了逻辑，就可以修改代码改掉bug。但做人工智能不一样，它是不确定性的，目前深度神经网络可能会有几百层，几千万个参数，我们没有严格的数学逻辑确定这些参数的意义，只能根据经验和一些原理进行调参，修改数据集等优化结果，过程中充满了不确定性。</p>
</li>
<li><p>第三，学习人工智能对于数学和英语有一定要求<br>特别是做模型优化的时候。数学方面需要知道一些微积分，线性代数，概率论的知识，英文方面需要能够比较顺利的看懂一些英文的资料和论文，例如很多著名模型的结构相关的论文都发表在arxiv上，需要能大概看懂这些论文。</p>
</li>
<li><p>第四，学习深度学习对于计算机有一定要求<br>模型训练在GPU上做能够明显加快速度，例如在Imagenet2012数据集上训练InceptionV3模型， 大概有130万张图片， 在一个nVidia Tesla P100 GPU上训练到95%的召回率需要3天，在普通CPU上训练可能需要几个星期。如果你学习一个东西，几星期后才能知道结果那肯定是让人崩溃的。万幸的是，现在Amazon，Goolge，阿里云，腾讯云等都有GPU服务器可以租用。</p>
</li>
</ul>
<h2 id="程序员学习人工智能的三个阶段"><a href="#程序员学习人工智能的三个阶段" class="headerlink" title="程序员学习人工智能的三个阶段"></a>程序员学习人工智能的三个阶段</h2><p>学习人工智能的三个阶段是 <em>应用，优化，和定义问题</em>，这个观点是我在某大牛的一篇文章中看到的，然后我针对程序员的学习特点做了一些优化。</p>
<p>应用应该是学习的第一步，就是想怎么样才能把人工智能应用到你的业务中，在这一步之前你需要对人工智能的现状有个基本清晰的认识，抛弃不实际的想法。具体的做法大概是针对自己的问题，收集数据，建立模型，可以用传统模型或深度学习模型。这一块程序员去做还是有一定优势的， 做到这一步需要对机器学习和深度学习有一定了解， 熟悉python， 能用tensorflow做一些模型训练。举个应用的例子，<a href="http://www.farmer.com.cn/jjpd/nyxxh/201710/t20171030_1333250.htm" target="_blank" rel="external">人工智能跨界农业实现“桃脸识别”</a>，用人工智能实现桃子的智能筛选。</p>
<p>优化是学习的第二步，这一步就有些难度了，需要对卷积神经网络的原理和计算过程很清楚，需要对tensorflow，caffe，pyTorch等框架比较熟悉，需要对Inception，Resnet等经典模型的结构有一定研究，能够看懂论文和代码，并能够结合数据进行分析和实验，对模型结构，参数等不断进行优化，达到提高识别率等目的， 这个优化过程需要耗费大量的时间。</p>
<p>定义问题是学习的第三步，能够做到这一步应该是大神级的存在了，例如谷歌大脑，AlphaGO，百度大脑等顶级团队和学术界的科学家和资深工程师等。这一步的难点是如果用数学语言定义清楚问题，并能够用工程上可行的机器学习算法进行优化求解。做到这一步是很难的，能够做到这一步的人应该是很少的，大部分都是大公司某个方向的领军人物。</p>
<h2 id="应该怎样转型人工智能"><a href="#应该怎样转型人工智能" class="headerlink" title="应该怎样转型人工智能"></a>应该怎样转型人工智能</h2><p>对于想转型人工智能的程序员， 我觉得首先要想清楚，自己想不想做这样一个不确定性的事情，自己的学习能力，数学和英语基础怎么样，不能看到行业热工资高就盲目转行。其实无论是前端，后端还是移动端，只要技术做到一定深度，收入都会不错，互联网行业的高薪还会持续很多年，做好技术可以有个不错的收入。</p>
<p>当然，学习和了解一些人工智能技术总是没有坏处的，可以先在自己感兴趣的领域尝试应用下人工智能技术，试试有没有这方面的兴趣和能力。毕竟IT行业变化很快，说不定几年后无论做什么开发都需要懂一些人工智能知识呢？</p>
<p>在人工智能这个热门的行业，早起的鸟有虫吃，但前提是鸟才行。</p>
<h2 id="大会介绍"><a href="#大会介绍" class="headerlink" title="大会介绍"></a>大会介绍</h2><p>“架构迎接未来变化”这是本届互联网架构峰会（Internet Architecture Summit，简称IAS）的主题思想，我们将以全新的“互联网结构视角”来看待当前发生着的一切，其中包括互联网的技术性范式和组织性范式。伴随着年初NJSD全球软件大会（NJSD Global）的脚步以及本次APSEW亚太软件工程周的技术节，12月初的南京，我们又将迎来一个引领行业发展的技术交流高峰!</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;strong&gt;本文是为了准备&lt;a href=&quot;http://njsd-china.org/IAS2017/&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;IAS2017&lt;/a&gt;互联网架构峰会而作，我将主持本次大会中《如何转型为一个人工智能工程师？》圆桌论坛的讨论，欢迎大家围观。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我是一个工作时间比较久的全栈工程师，做过web开发，前端，后端，移动端，HTML5的开发。2016年底开始学习机器学习，做深度学习大概不到半年。目前在研究手机端侧人工智能。在人工智能技术方面我肯定没有研究人工智能很多年的人有经验，但在怎样转型人工智能方面我还是有些体会的。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>如何使用Tensorflow slim模型训练自己的数据集</title>
    <link href="http://offbye.com/2018/07/18/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Tensorflow-slim%E6%A8%A1%E5%9E%8B%E8%AE%AD%E7%BB%83%E8%87%AA%E5%B7%B1%E7%9A%84%E6%95%B0%E6%8D%AE%E9%9B%86/"/>
    <id>http://offbye.com/2018/07/18/如何使用Tensorflow-slim模型训练自己的数据集/</id>
    <published>2018-07-18T15:19:07.000Z</published>
    <updated>2018-07-18T15:28:27.794Z</updated>
    
    <content type="html"><![CDATA[<h2 id="TfSlim简介"><a href="#TfSlim简介" class="headerlink" title="TfSlim简介"></a>TfSlim简介</h2><h2 id="TfSlim提供的预训练模型"><a href="#TfSlim提供的预训练模型" class="headerlink" title="TfSlim提供的预训练模型"></a>TfSlim提供的预训练模型</h2><h2 id="准备数据集，生成TFRecord文件"><a href="#准备数据集，生成TFRecord文件" class="headerlink" title="准备数据集，生成TFRecord文件"></a>准备数据集，生成TFRecord文件</h2><ol>
<li><p>整理自己的图片数据集目录结构<br>数据集根目录下建立train和val2个文件夹，分布放置训练数据和验证数据， 每个类别一个目录</p>
<a id="more"></a>
</li>
<li><p>生成TFRecord文件<br>参考<br><a href="https://github.com/tensorflow/models/tree/master/research/inception#how-to-construct-a-new-dataset-for-retraining" target="_blank" rel="external">https://github.com/tensorflow/models/tree/master/research/inception#how-to-construct-a-new-dataset-for-retraining</a></p>
</li>
</ol>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#</span> location to where to save the TFRecord data.</span><br><span class="line">OUTPUT_DIRECTORY=$HOME/my-custom-data/</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> build the preprocessing script.</span><br><span class="line">cd tensorflow-models/inception</span><br><span class="line">bazel build //inception:build_image_data</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span> convert the data.</span><br><span class="line">bazel-bin/inception/build_image_data \</span><br><span class="line">  --train_directory="$&#123;TRAIN_DIR&#125;" \</span><br><span class="line">  --validation_directory="$&#123;VALIDATION_DIR&#125;" \</span><br><span class="line">  --output_directory="$&#123;OUTPUT_DIRECTORY&#125;" \</span><br><span class="line">  --labels_file="$&#123;LABELS_FILE&#125;" \</span><br><span class="line">  --train_shards=128 \</span><br><span class="line">  --validation_shards=24 \</span><br><span class="line">  --num_threads=8</span><br></pre></td></tr></table></figure>
<p>在<code>research/slim/datasets</code>下创建自己的dataset文件，例如mydata.py<br>把flowers.py中内容复制过来，<br>按照数据实际情况修改下面几行：<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">_FILE_PATTERN = <span class="string">'flowers_%s_*.tfrecord'</span></span><br><span class="line"></span><br><span class="line">SPLITS_TO_SIZES = &#123;<span class="string">'train'</span>: <span class="number">3320</span>, <span class="string">'validation'</span>: <span class="number">350</span>&#125;</span><br><span class="line"></span><br><span class="line">_NUM_CLASSES = <span class="number">5</span></span><br><span class="line"></span><br><span class="line">_ITEMS_TO_DESCRIPTIONS = &#123;</span><br><span class="line">    <span class="string">'image'</span>: <span class="string">'A color image of varying size.'</span>,</span><br><span class="line">    <span class="string">'label'</span>: <span class="string">'A single integer between 0 and 4'</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">```python</span><br><span class="line">修改`research/slim/datasets/dataset_factory.py`， 增加自己的数据集mydata</span><br><span class="line"><span class="keyword">from</span> datasets <span class="keyword">import</span> mydata</span><br><span class="line"></span><br><span class="line">datasets_map = &#123;</span><br><span class="line">    <span class="string">'cifar10'</span>: cifar10,</span><br><span class="line">    <span class="string">'flowers'</span>: flowers,</span><br><span class="line">    <span class="string">'imagenet'</span>: imagenet,</span><br><span class="line">    <span class="string">'mnist'</span>: mnist,</span><br><span class="line">    <span class="string">'mydata'</span>: mydata,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">## 从头训练模型（From Scratch）</span></span><br><span class="line"></span><br><span class="line">具体参数需要按照实际训练情况修改</span><br><span class="line">```shell</span><br><span class="line"></span><br><span class="line"> CUDA_VISIBLE_DEVICES=<span class="number">2</span>  nohup  python train_image_classifier.py   --train_dir=/tmp/md_train     --dataset_name=mydata   --dataset_split_name=train     --dataset_dir=/data5/mydata_tfrecording/   --model_name=mobilenet_v1  &gt; /tmp/md.txt &amp;</span><br></pre></td></tr></table></figure></p>
<h2 id="基于预训练模型优化（fune-turning）"><a href="#基于预训练模型优化（fune-turning）" class="headerlink" title="基于预训练模型优化（fune turning）"></a>基于预训练模型优化（fune turning）</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CUDA_VISIBLE_DEVICES=3 nohup  \</span><br><span class="line">python train_image_classifier.py \</span><br><span class="line">   --train_dir=/tmp/m2_train \</span><br><span class="line">   --dataset_dir=/data5/zxt/fdata/log \</span><br><span class="line">   --dataset_name=dishes \</span><br><span class="line">   --dataset_split_name=train \</span><br><span class="line">   --model_name=mobilenet_v1 \</span><br><span class="line">   --checkpoint_path=/data5/model/mobilenet_v1_1.0_224.ckpt</span><br><span class="line">   --checkpoint_exclude_scopes=MobilenetV1/Logits,MobilenetV1/AuxLogits \</span><br><span class="line">  --trainable_scopes=MobilenetV1/Logits,MobilenetV1/AuxLogits &gt; /tmp/m3.txt &amp;</span><br></pre></td></tr></table></figure>
<h2 id="评估模型"><a href="#评估模型" class="headerlink" title="评估模型"></a>评估模型</h2><p>修改代码错误<code>research/slim/eval_image_classifier.py</code> , 具体错误参考<a href="https://github.com/tensorflow/models/issues/694" target="_blank" rel="external">https://github.com/tensorflow/models/issues/694</a><br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#line 156修改</span></span><br><span class="line">Change</span><br><span class="line">slim.metrics.streaming_recall_at_k(logits, labels, <span class="number">5</span>)</span><br><span class="line">to</span><br><span class="line">slim.metrics.streaming_sparse_recall_at_k(logits, labels, <span class="number">5</span>)</span><br></pre></td></tr></table></figure></p>
<p>然后运行就可以了！<br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CUDA_VISIBLE_DEVICES=2 python eval_image_classifier.py     --alsologtostderr     --checkpoint_path=/tmp/m2_train/ --eval_dir=/tmp/m2_eval --dataset_dir=/data5/zxt/fdata/log     --dataset_name=dishes     --dataset_split_name=validation --model_name=mobilenet_v1</span><br></pre></td></tr></table></figure></p>
<p>最后放个可以同时训练多个模型的python脚本</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> tensorflow <span class="keyword">as</span> tf</span><br><span class="line"></span><br><span class="line">slim = tf.contrib.slim</span><br><span class="line"></span><br><span class="line">SLIM_DIR = <span class="string">'/data5/zxt/models/research/slim/'</span></span><br><span class="line">LOG_DIR = <span class="string">'/data5/zxt/flowers/train_log/'</span></span><br><span class="line">MODELS = [<span class="string">'inception_v3'</span>, <span class="string">'inception_resnet_v2'</span>]</span><br><span class="line"></span><br><span class="line">model = <span class="string">"inception_v4"</span></span><br><span class="line">DATASET_NAME = <span class="string">'flowers'</span></span><br><span class="line">DATASET_DIR = <span class="string">'/data5/zxt/flowers/log'</span></span><br><span class="line"></span><br><span class="line">CMD_TRAIN = <span class="string">'CUDA_VISIBLE_DEVICES=&#123;0&#125; nohup python train_image_classifier.py --learning_rate=0.01 --num_epochs_per_decay=2.0 --optimizer=adam --train_dir=&#123;1&#125;/&#123;2&#125;_train '</span> \</span><br><span class="line">            <span class="string">'--dataset_name=&#123;3&#125; --dataset_dir=&#123;4&#125; --dataset_split_name=train --model_name=&#123;2&#125; &gt; &#123;1&#125;/&#123;3&#125;_&#123;2&#125;_train.txt &amp; '</span></span><br><span class="line">CMD_VAL = <span class="string">'CUDA_VISIBLE_DEVICES=&#123;0&#125; nohup python eval_image_classifier.py --alsologtostderr --checkpoint_path=&#123;1&#125;/&#123;2&#125;_train'</span> \</span><br><span class="line">          <span class="string">' --eval_dir=&#123;1&#125;/&#123;2&#125;_eval  --dataset_name=&#123;3&#125; --dataset_dir=&#123;4&#125; --dataset_split_name=validation '</span> \</span><br><span class="line">          <span class="string">'--model_name=&#123;2&#125; --preprocessing_name inception --eval_image_size 299 --eval_loop=True &gt; &#123;1&#125;/&#123;3&#125;_&#123;2&#125;_eval.txt &amp;'</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">race</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="comment"># os.makedirs(LOG_DIR)</span></span><br><span class="line">    <span class="keyword">for</span> index, model <span class="keyword">in</span> enumerate(MODELS):</span><br><span class="line">        index += <span class="number">1</span></span><br><span class="line">        <span class="comment"># print(index)</span></span><br><span class="line">        cmd_train = CMD_TRAIN.format(index * <span class="number">2</span> - <span class="number">1</span>, LOG_DIR, model, DATASET_NAME, DATASET_DIR)</span><br><span class="line">        cmd_eval = CMD_VAL.format(index*<span class="number">2</span>, LOG_DIR, model, DATASET_NAME, DATASET_DIR)</span><br><span class="line"></span><br><span class="line">        print(cmd_train)</span><br><span class="line">        print(cmd_eval)</span><br><span class="line">        <span class="comment"># os.system(cmd_train)</span></span><br><span class="line">        <span class="comment"># os.system(cmd_eval)</span></span><br><span class="line">        <span class="comment"># os.system("CUDA_VISIBLE_DEVICES=0 tensorboard --logdir &#123;0&#125; &amp;".format(LOG_DIR))</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    race()</span><br></pre></td></tr></table></figure>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul>
<li><a href="https://github.com/tensorflow/models/blob/master/research/slim/README.md" target="_blank" rel="external">https://github.com/tensorflow/models/blob/master/research/slim/README.md</a></li>
</ul>
<!-- more -->
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;TfSlim简介&quot;&gt;&lt;a href=&quot;#TfSlim简介&quot; class=&quot;headerlink&quot; title=&quot;TfSlim简介&quot;&gt;&lt;/a&gt;TfSlim简介&lt;/h2&gt;&lt;h2 id=&quot;TfSlim提供的预训练模型&quot;&gt;&lt;a href=&quot;#TfSlim提供的预训练模型&quot; class=&quot;headerlink&quot; title=&quot;TfSlim提供的预训练模型&quot;&gt;&lt;/a&gt;TfSlim提供的预训练模型&lt;/h2&gt;&lt;h2 id=&quot;准备数据集，生成TFRecord文件&quot;&gt;&lt;a href=&quot;#准备数据集，生成TFRecord文件&quot; class=&quot;headerlink&quot; title=&quot;准备数据集，生成TFRecord文件&quot;&gt;&lt;/a&gt;准备数据集，生成TFRecord文件&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;p&gt;整理自己的图片数据集目录结构&lt;br&gt;数据集根目录下建立train和val2个文件夹，分布放置训练数据和验证数据， 每个类别一个目录&lt;/p&gt;
    
    </summary>
    
    
      <category term="tensorflow" scheme="http://offbye.com/tags/tensorflow/"/>
    
      <category term="slim" scheme="http://offbye.com/tags/slim/"/>
    
  </entry>
  
  <entry>
    <title>Redex安卓Apk优化技术研究</title>
    <link href="http://offbye.com/2017/03/08/Redex%E5%AE%89%E5%8D%93Apk%E4%BC%98%E5%8C%96%E6%8A%80%E6%9C%AF%E7%A0%94%E7%A9%B6/"/>
    <id>http://offbye.com/2017/03/08/Redex安卓Apk优化技术研究/</id>
    <published>2017-03-08T13:43:28.000Z</published>
    <updated>2017-03-08T13:44:15.000Z</updated>
    
    <content type="html"><![CDATA[<p>ReDex 是 Facebook 开源的工具，通过对字节码进行优化，以减小 Android Apk 大小，同时提高 App 启动速度。<br>GitHub：<a href="https://github.com/facebook/redex" target="_blank" rel="external">ReDex github</a>，官网主页：fbredex.com</p>
<p>本次研究完成了Redex在Ubuntu linux上的安装和配置，进行了Redex优化测试， 实验了Redex优化的主要流程, 包括Inderdex。<br><a id="more"></a></p>
<h2 id="Redex优化的基础知识"><a href="#Redex优化的基础知识" class="headerlink" title="Redex优化的基础知识"></a>Redex优化的基础知识</h2><p>可以先看看这几篇文章：</p>
<ul>
<li><a href="http://mp.weixin.qq.com/s?__biz=MzAwMTYwNzE2Mg==&amp;mid=2651036594&amp;idx=1&amp;sn=b276c0f76cea713e5d568ab51e3f7f13&amp;scene=0#wechat_redirect" target="_blank" rel="external">基于 Facebook Redex 实现 Android APK 的压缩和优化</a></li>
<li><a href="http://www.trinea.cn/android/facebook%E5%BC%80%E6%BA%90%E7%9A%84android%E4%BC%98%E5%8C%96%E5%B7%A5%E5%85%B7redex-%E5%87%8F%E5%B0%8F%E5%AE%89%E8%A3%85%E5%8C%85%E5%A4%A7%E5%B0%8F-%E5%90%8C%E6%97%B6%E6%8F%90%E9%AB%98%E8%BF%90/" target="_blank" rel="external">Facebook App 优化工具 ReDex 优化的 6 点及未优化的一大方面</a></li>
<li><a href="https://code.facebook.com/posts/1480969635539475/optimizing-android-bytecode-with-redex" target="_blank" rel="external">Optimizing Android bytecode with ReDex</a></li>
</ul>
<h2 id="Ubuntu上安装Redex"><a href="#Ubuntu上安装Redex" class="headerlink" title="Ubuntu上安装Redex"></a>Ubuntu上安装Redex</h2><p>Redex目前支持Ubuntu Linux和Mac系统， 安装时需要编译源码，Ubuntu下面需要有sudo权限才能安装。<br>安装过程参考官方文档。</p>
<h3 id="Ubuntu-14-04-LTS-64-bit"><a href="#Ubuntu-14-04-LTS-64-bit" class="headerlink" title="Ubuntu 14.04 LTS (64-bit)"></a>Ubuntu 14.04 LTS (64-bit)</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install \</span><br><span class="line">    g++ \</span><br><span class="line">    automake \</span><br><span class="line">    autoconf \</span><br><span class="line">    autoconf-archive \</span><br><span class="line">    libtool \</span><br><span class="line">    libboost-all-dev \</span><br><span class="line">    liblz4-dev \</span><br><span class="line">    liblzma-dev \</span><br><span class="line">    make \</span><br><span class="line">    zlib1g-dev \</span><br><span class="line">    binutils-dev \</span><br><span class="line">    libjemalloc-dev \</span><br><span class="line">    libiberty-dev \</span><br><span class="line">    libjsoncpp-dev</span><br></pre></td></tr></table></figure>
<h3 id="Download-Build-and-Install"><a href="#Download-Build-and-Install" class="headerlink" title="Download, Build and Install"></a>Download, Build and Install</h3><p>Get ReDex from GitHub:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/facebook/redex.git</span><br><span class="line">cd redex</span><br></pre></td></tr></table></figure></p>
<p>Now, build ReDex using autoconf and make.<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">autoreconf -ivf &amp;&amp; ./configure &amp;&amp; make</span><br><span class="line">sudo make install</span><br></pre></td></tr></table></figure></p>
<p>然后就可以在命令行下运行Redex了</p>
<h2 id="Redex-Indexdex介绍"><a href="#Redex-Indexdex介绍" class="headerlink" title="Redex Indexdex介绍"></a>Redex Indexdex介绍</h2><p>Interdex优化比较复杂，默认配置是不开启的，具体看<a href="https://github.com/facebook/redex/blob/master/docs/Interdex.md" target="_blank" rel="external">Interdex文档</a>。<br>Interdex Pass 可以优化dex中class的顺序，以及class在不同的dex中的分布（如果是app使用了multidex）<br>按照class在实际运行中调用的顺序在dex中进行重新排序，可以带来几个好处：</p>
<ul>
<li>更少的IO</li>
<li>更少的内存占用</li>
<li>更少page cache污染</li>
</ul>
<p>Redex默认的配置文件是不包含Inderdex这一步的。增加Inderdex后的配置文件如下：<br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;redex&quot; : &#123;</span><br><span class="line">    &quot;passes&quot; : [</span><br><span class="line">      &quot;ReBindRefsPass&quot;,</span><br><span class="line">      &quot;BridgePass&quot;,</span><br><span class="line">      &quot;SynthPass&quot;,</span><br><span class="line">      &quot;FinalInlinePass&quot;,</span><br><span class="line">      &quot;DelSuperPass&quot;,</span><br><span class="line">      &quot;SingleImplPass&quot;,</span><br><span class="line">      &quot;SimpleInlinePass&quot;,</span><br><span class="line">      &quot;StaticReloPass&quot;,</span><br><span class="line">      &quot;RemoveEmptyClassesPass&quot;,</span><br><span class="line">      &quot;ShortenSrcStringsPass&quot;,</span><br><span class="line">      &quot;InterDexPass&quot;</span><br><span class="line">    ],</span><br><span class="line">   &quot;coldstart_classes&quot;:&quot;app_list_of_classes.txt&quot; //class调用顺序列表</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="生成输入数据"><a href="#生成输入数据" class="headerlink" title="生成输入数据"></a>生成输入数据</h2><p>如何得到实际运行中class的调用顺序？</p>
<h3 id="首先需要收集app的运行数据"><a href="#首先需要收集app的运行数据" class="headerlink" title="首先需要收集app的运行数据"></a>首先需要收集app的运行数据</h3><p>按照典型使用场景操作app，获取heap dump文件， 使用redex提供的脚本<code>redex/tools/hprof/dump_classes_from_hprof.py</code>分析dump文件，得到class列表。<br>这里有个坑，首先是dump_classes_from_hprof.py在python2运行都有错误， Python2需要安装<a href="https://pypi.python.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz#md5=5f13a0841a61f7fc295c514490d120d0" target="_blank" rel="external">enum34</a>后才能正常运行， 不兼容python3<br>在ubuntu上安装enum34后，用python2.7运行，可以得到class列表</p>
<h3 id="具体操作过程如下"><a href="#具体操作过程如下" class="headerlink" title="具体操作过程如下"></a>具体操作过程如下</h3><p>// get the process if of your app<br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb shell ps | grep YOUR_APP_NAME | awk &apos;&#123;print $2&#125;&apos; &gt; YOUR_PID ( if you don&apos;t have awk, the second value is the pid of your app)</span><br></pre></td></tr></table></figure></p>
<p> // dump the heap of your app. You WILL NEED ROOT for this step<br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">adb root</span><br><span class="line">adb shell am dumpheap YOUR_PID /data/local/tmp/SOMEDUMP.hprof</span><br></pre></td></tr></table></figure></p>
<p> // copy the heap to your host computer<br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">adb pull /data/local/tmp/SOMEDUMP.hprof YOUR_DIR_HERE/.</span><br></pre></td></tr></table></figure></p>
<p> // pass the heap dump to the python script for parsing and printing out the class list<br> // Note that the script needs python 2<br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">YOUR_PYTHON_2_PATH redex/tools/hprof/dump_classes_from_hprof.py --hprof YOUR_DIR_HERE/SOMEDUMP.hprof &gt; list_of_classes.txt</span><br></pre></td></tr></table></figure></p>
<h3 id="测量优化效果"><a href="#测量优化效果" class="headerlink" title="测量优化效果"></a>测量优化效果</h3><p>主要是看app内存占用和 .dex mmap<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">adb shell ps | grep com.test.app | awk &apos;&#123; print $2 &#125;&apos;</span><br><span class="line">9003</span><br><span class="line"></span><br><span class="line">[R:\AndroidM\packages\apps]$ adb shell dumpsys meminfo 9003</span><br><span class="line">Applications Memory Usage (kB):</span><br><span class="line">Uptime: 2329984 Realtime: 2329984</span><br><span class="line"></span><br><span class="line">** MEMINFO in pid 9003 [com.test.app] **</span><br><span class="line">                   Pss  Private  Private  Swapped     Heap     Heap     Heap</span><br><span class="line">                 Total    Dirty    Clean    Dirty     Size    Alloc     Free</span><br><span class="line">                ------   ------   ------   ------   ------   ------   ------</span><br><span class="line">  Native Heap    10711    10044        0        0    44416    40455     3960</span><br><span class="line">  Dalvik Heap     2201     2172        0        0    35719    33937     1782</span><br><span class="line"> Dalvik Other     5424     4984        0        0                           </span><br><span class="line">        Stack      516      516        0        0                           </span><br><span class="line">       Ashmem        4        0        0        0                           </span><br><span class="line">    Other dev        5        0        4        0                           </span><br><span class="line">     .so mmap      967      152      148      360                           </span><br><span class="line">    .apk mmap      271        0       56        0                           </span><br><span class="line">    .ttf mmap        8        0        0        0                           </span><br><span class="line">    .dex mmap     4531        8     4464        0                           </span><br><span class="line">    .oat mmap     2274        0      776        0                           </span><br><span class="line">    .art mmap     2761     1352     1020        0                           </span><br><span class="line">   Other mmap       94        8        8        0                           </span><br><span class="line">    GL mtrack     4196     4196        0        0                           </span><br><span class="line">      Unknown      190      188        0        0                           </span><br><span class="line">        TOTAL    34153    23620     6476      360    80135    74392     5742</span><br><span class="line"></span><br><span class="line"> App Summary</span><br><span class="line">                       Pss(KB)</span><br><span class="line">                        ------</span><br><span class="line">           Java Heap:     4544</span><br><span class="line">         Native Heap:    10044</span><br><span class="line">                Code:     5604</span><br><span class="line">               Stack:      516</span><br><span class="line">            Graphics:     4196</span><br><span class="line">       Private Other:     5192</span><br><span class="line">              System:     4057</span><br><span class="line"></span><br><span class="line">               TOTAL:    34153      TOTAL SWAP (KB):      360</span><br><span class="line"></span><br><span class="line"> Objects</span><br><span class="line">               Views:       48         ViewRootImpl:        0</span><br><span class="line">         AppContexts:        2           Activities:        1</span><br><span class="line">              Assets:        3        AssetManagers:        2</span><br><span class="line">       Local Binders:       12        Proxy Binders:       27</span><br><span class="line">       Parcel memory:       13         Parcel count:       52</span><br><span class="line">    Death Recipients:        0      OpenSSL Sockets:        0</span><br><span class="line"></span><br><span class="line"> SQL</span><br><span class="line">         MEMORY_USED:      663</span><br><span class="line">  PAGECACHE_OVERFLOW:       88          MALLOC_SIZE:       62</span><br><span class="line"></span><br><span class="line"> DATABASES</span><br><span class="line">      pgsz     dbsz   Lookaside(b)          cache  Dbname</span><br><span class="line">         4       68            512      225/36/22  /data/user/0/com.test.app/databases/MyTicket</span><br></pre></td></tr></table></figure></p>
<h2 id="App冷启动时间测试"><a href="#App冷启动时间测试" class="headerlink" title="App冷启动时间测试"></a>App冷启动时间测试</h2><p>我们常说的App冷启动,是指启动时你的应用程序的进程是没有创建的. 这也是大部分应用的使用场景.用户在桌面上点击你应用的 icon 之后,首先要创建进程,然后才启动 MainActivity.<br>这时候<code>adb shell am start -W packagename/MainActivity</code> 返回的结果,就是标准的应用程序的启动时间（注意 Android 5.0 之前的手机是没有 WaitTime 这个值的）<br>具体可以参考<a href="https://www.zhihu.com/question/35487841" target="_blank" rel="external">怎么计算apk的启动时间？</a><br>如果只关心某个应用自身启动耗时，参考TotalTime；如果关心系统启动应用耗时，参考WaitTime；如果关心应用有界面Activity启动耗时，参考ThisTime。</p>
<p>我编写了一个python脚本，可以自动进行多次冷启动，并画出启动时间统计图，计算平均启动时间。用这个脚本可以很方便的测量任意app的启动时间。<br>打开app，马上运行<code>adb shell dumpsys activity  top</code>，可以看到app的包名和启动Activity， 测试脚本需要输入包名和启动activity的完整类名。</p>
<h3 id="Redex优化效果分析"><a href="#Redex优化效果分析" class="headerlink" title="Redex优化效果分析"></a>Redex优化效果分析</h3><p>使用以前开发的App做测试，体积20M，使用了multidex。由于app有启动页，本身启动速度已经很快，1s多一点，因此优化效果不够明显。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">优化前数据</span><br><span class="line">[&apos;465&apos;, &apos;1122&apos;, &apos;1163&apos;]</span><br><span class="line">[[&apos;444&apos;, &apos;1123&apos;, &apos;1149&apos;], [&apos;440&apos;, &apos;1150&apos;, &apos;1191&apos;], [&apos;410&apos;, &apos;1450&apos;, &apos;1520&apos;], [&apos;439&apos;, &apos;1081&apos;, &apos;1112&apos;], [&apos;419&apos;, &apos;1072&apos;, &apos;1117&apos;], [&apos;409&apos;, &apos;1055&apos;, &apos;1084&apos;], [&apos;423&apos;, &apos;1101&apos;, &apos;1135&apos;], [&apos;427&apos;, &apos;1079&apos;, &apos;1120&apos;], [&apos;465&apos;, &apos;1122&apos;, &apos;1163&apos;]]</span><br><span class="line">apk launcher avarage times:[ThisTime, TotalTime, WaitTime]</span><br><span class="line">[  430.66665649  1137.          1176.77783203]</span><br><span class="line"></span><br><span class="line">redex优化后的数据，优化后TotalTime减少70ms</span><br><span class="line">[&apos;453&apos;, &apos;1129&apos;, &apos;1159&apos;]</span><br><span class="line">[[&apos;403&apos;, &apos;1072&apos;, &apos;1099&apos;], [&apos;411&apos;, &apos;1077&apos;, &apos;1123&apos;], [&apos;414&apos;, &apos;1056&apos;, &apos;1085&apos;], [&apos;383&apos;, &apos;1031&apos;, &apos;1077&apos;], [&apos;386&apos;, &apos;1056&apos;, &apos;1102&apos;], [&apos;381&apos;, &apos;1030&apos;, &apos;1071&apos;], [&apos;449&apos;, &apos;1111&apos;, &apos;1148&apos;], [&apos;390&apos;, &apos;1095&apos;, &apos;1127&apos;], [&apos;453&apos;, &apos;1129&apos;, &apos;1159&apos;]]</span><br><span class="line">apk launcher avarage times:[ThisTime, TotalTime, WaitTime]</span><br><span class="line">[  407.777771    1073.          1110.11108398]</span><br></pre></td></tr></table></figure>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>Redex可以在Proguard优化后再在dex层面进行优化，Redex需要配置Proguard配置文件来保护一些不应该被优化的类（如JNI调用、反射调用的类等）。<br>根据实际测试结果看Redex优化后可以提升冷启动速度10%左右，apk体积减少100k左右，低于Facebook给出的数据（25%）。原因可能是我测试的Apk比较简单，本身启动速度已经比较快，后面应该找启动速度慢的App进行优化测试。<br>普通App建议在做了Proguard优化后，再根据冷启动测试数据决定是否做Redex优化。</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://github.com/facebook/redex" target="_blank" rel="external">ReDex github</a><br><a href="https://code.facebook.com/posts/1480969635539475/optimizing-android-bytecode-with-redex" target="_blank" rel="external">Optimizing Android bytecode with ReDex</a><br><a href="http://mp.weixin.qq.com/s?__biz=MzAwMTYwNzE2Mg==&amp;mid=2651036594&amp;idx=1&amp;sn=b276c0f76cea713e5d568ab51e3f7f13&amp;scene=0#wechat_redirect" target="_blank" rel="external">基于 Facebook Redex 实现 Android APK 的压缩和优化</a><br><a href="http://www.trinea.cn/android/facebook%E5%BC%80%E6%BA%90%E7%9A%84android%E4%BC%98%E5%8C%96%E5%B7%A5%E5%85%B7redex-%E5%87%8F%E5%B0%8F%E5%AE%89%E8%A3%85%E5%8C%85%E5%A4%A7%E5%B0%8F-%E5%90%8C%E6%97%B6%E6%8F%90%E9%AB%98%E8%BF%90/" target="_blank" rel="external">Facebook App 优化工具 ReDex 优化的 6 点及未优化的一大方面</a></p>
<p><a href="http://www.jianshu.com/p/236f5ac79520" target="_blank" rel="external">浅谈Android启动时间</a><br><a href="https://www.zhihu.com/question/35487841" target="_blank" rel="external">怎么计算apk的启动时间？</a></p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;ReDex 是 Facebook 开源的工具，通过对字节码进行优化，以减小 Android Apk 大小，同时提高 App 启动速度。&lt;br&gt;GitHub：&lt;a href=&quot;https://github.com/facebook/redex&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;ReDex github&lt;/a&gt;，官网主页：fbredex.com&lt;/p&gt;
&lt;p&gt;本次研究完成了Redex在Ubuntu linux上的安装和配置，进行了Redex优化测试， 实验了Redex优化的主要流程, 包括Inderdex。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>安卓源码项目进行gradle编译改造常见问题解决</title>
    <link href="http://offbye.com/2017/03/08/%E5%AE%89%E5%8D%93%E6%BA%90%E7%A0%81%E9%A1%B9%E7%9B%AE%E8%BF%9B%E8%A1%8Cgradle%E7%BC%96%E8%AF%91%E6%94%B9%E9%80%A0%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"/>
    <id>http://offbye.com/2017/03/08/安卓源码项目进行gradle编译改造常见问题解决/</id>
    <published>2017-03-08T13:42:12.000Z</published>
    <updated>2017-03-08T13:43:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近把依赖安卓源码编译环境的多个项目切换到gradle编译环境，需要把一些项目改成lib库依赖，基本思路是把通用的代码和第三方jar放在一个utils库项目中， 其他子项目改为lib库，子项目统一依赖utils，主项目依赖9个子项目，解除子项目对主项目的依赖，最后gradle编译成一个apk。<br><a id="more"></a></p>
<p>我整理了一些遇到的常见问题，希望对大家有帮助。<br>首先注意的gradle编译比mm方式编译对项目质量的要求更严格，特别是编译release版本，很多命名不规范的资源需要修改， strings缺少的翻译等需要对齐。</p>
<h2 id="问题一："><a href="#问题一：" class="headerlink" title="问题一："></a>问题一：</h2><p>Error:(26, 9) Attribute application@icon value=(@drawable/logo) from AndroidManifest.xml:26:9<br>Error:(28, 9) Attribute application@theme value=(@style/ThemeActionBar) from AndroidManifest.xml:28:9<br>is also present at XXXX-trunk:XXXXLib:unspecified:15:9 value=(@style/AppTheme)<br>Suggestion: add ‘tools:replace=”android:theme”‘ to <application> element at AndroidManifest.xml:24:5 to override<br>Error:Execution failed for task ‘:XXXX:processDebugManifest’.</application></p>
<blockquote>
<p>Manifest merger failed with multiple errors, see logs</p>
</blockquote>
<p>原因：<br>AS的Gradle插件默认会启用Manifest Merger Tool，若Library项目中也定义了与主项目相同的属性（例如默认生成的Android:icon和android:theme），则此时会合并失败，并报上面的错误。</p>
<p>解决方法有以下2种：<br>方法1：在Manifest.xml的application标签下添加tools:replace=”android:icon, android:theme”（多个属性用,隔开，并且记住在manifest根标签上加入xmlns:tools=”<a href="http://schemas.android.com/tools&quot;，否则会找不到namespace哦）" target="_blank" rel="external">http://schemas.android.com/tools&quot;，否则会找不到namespace哦）</a><br>方法2：在build.gradle根标签上加上useOldManifestMerger true （懒人方法）</p>
<p>参考官方介绍：<br><a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger" target="_blank" rel="external">http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger</a></p>
<h2 id="问题二："><a href="#问题二：" class="headerlink" title="问题二："></a>问题二：</h2><p>Library Project里面的BuildConfig.DEBUG永远都是false。这是Android Studio的一个已知问题，某Google的攻城狮说，Library projects目前只会生成release的包。<br>Issue 52962: <a href="https://code.google.com/p/android/issues/detail?id=52962" target="_blank" rel="external">https://code.google.com/p/android/issues/detail?id=52962</a></p>
<p>解决方法：（某Google的攻城狮推荐的方法）<br>Workaround: instaed of BuildConfig.DEBUG create another boolean variable at lib-project’s e.g. BuildConfig.RELEASE and link it with application’s buildType.<br><a href="https://gist.github.com/almozavr/d59e770d2a6386061fcb" target="_blank" rel="external">https://gist.github.com/almozavr/d59e770d2a6386061fcb</a></p>
<p>参考stackoverflow上的这篇帖：<br><a href="http://stackoverflow.com/questions/20176284/buildconfig-debug-always-false-when-building-library-projects-with-gradle" target="_blank" rel="external">http://stackoverflow.com/questions/20176284/buildconfig-debug-always-false-when-building-library-projects-with-gradle</a></p>
<h2 id="问题三："><a href="#问题三：" class="headerlink" title="问题三："></a>问题三：</h2><p>Duplicate zip entry error， 这个问题一般是引用了不同版本的com.android.support库，或者不同的jar里面有同名的class文件导致的，需要人工排查了。</p>
<p>解决方法：<br>Settings-&gt;IDE Settings-&gt;Editor-&gt;Other-&gt;Strip trailing spaces on Save-&gt;None</p>
<h2 id="问题四："><a href="#问题四：" class="headerlink" title="问题四："></a>问题四：</h2><p>编译的时候，报：Failure [INSTALL_FAILED_OLDER_SDK]。一般是系统自动帮你设置了compileSdkVersion</p>
<p>解决方法：<br>修改build.gradle下的compileSdkVersion ‘android-L’为compileSdkVersion 24</p>
<h2 id="问题五："><a href="#问题五：" class="headerlink" title="问题五："></a>问题五：</h2><p>Error:Executionfailed for task ‘:greencar:processDebugManifest’.&gt; Manifest merger failed withmultipleerrors, see logs。<br>原因：AS的Gradle插件默认会启用Manifest Merger Tool，若Library项目中也定义了与主项目相同的属性（例如默认生成的android:icon和android:theme），则此时会合并失败，并报上面的错误。<br>解决方案：<br>在manifest根标签上加入<code>xmlns:tools=&quot;http://schemas.android.com/tools&quot;</code>，并在Manifest.xml的application标签下添加<code>tools:replace=&quot;name，icon, label，theme&quot;</code><br>以及在主项目的 manifest文件中，重复写了 call_phone的权限，网上也有人是某个activity下多写了一句intent-filter，里面没有内容，将这些重复的空的删掉就好，并将作为lib的minisdk与主项目同步（修改library飞build.gradle文件中最小sdk，或者降低主项目的sdk）</p>
<h2 id="问题六："><a href="#问题六：" class="headerlink" title="问题六："></a>问题六：</h2><p>在作为library的项目中报错：需要常量表达式<br>解决方案：<br>在一般的Android项目中，R类的常量都是用final定义的，但ADT 14之后，如果在library 项目中，它会没有final关键字，而我们在作为library的项目中使用了switch ，在switch语句的case中，如果使用 R.id.xxx 则会提示有问题，不允许非常量在case语句中。<br>Google提供的一个方法就是把它转化为if-else语句。目前我也是用了这个笨办法，还好Android studio 只要按Anter+Enter自动帮改，就是比较烦</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近把依赖安卓源码编译环境的多个项目切换到gradle编译环境，需要把一些项目改成lib库依赖，基本思路是把通用的代码和第三方jar放在一个utils库项目中， 其他子项目改为lib库，子项目统一依赖utils，主项目依赖9个子项目，解除子项目对主项目的依赖，最后gradle编译成一个apk。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>微信小程序开发者工具初体验及实现技术初探</title>
    <link href="http://offbye.com/2017/01/10/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%BC%80%E5%8F%91%E8%80%85%E5%B7%A5%E5%85%B7%E5%88%9D%E4%BD%93%E9%AA%8C%E5%8F%8A%E5%AE%9E%E7%8E%B0%E6%8A%80%E6%9C%AF%E5%88%9D%E6%8E%A2/"/>
    <id>http://offbye.com/2017/01/10/微信小程序开发者工具初体验及实现技术初探/</id>
    <published>2017-01-10T14:14:50.000Z</published>
    <updated>2017-01-10T15:07:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>微信小程序是当前的热点，市面上已经有很多微信小程序开发相关的文章，今天晚上抽了点时间折腾了微信小程序，并顺便看看了下微信小程序开发者工具的实现，是使用Node.js开发的，UI是基于NW.js框架开发的，支持Windows和Mac跨平台。<br><a id="more"></a></p>
<h2 id="微信小程序开发工具初体验"><a href="#微信小程序开发工具初体验" class="headerlink" title="微信小程序开发工具初体验"></a>微信小程序开发工具初体验</h2><p>首先当然是先下载个微信小程序开发工具，<a href="https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=201715" target="_blank" rel="external">微信Web开发者工具下载地址</a>, 然后顺便找了个感觉还可以的小程序源码<a href="https://github.com/RebeccaHanjw/weapp-wechat-zhihu" target="_blank" rel="external">微信中的知乎–微信小程序 demo</a>。目前支持Win32，Win64，Mac版本。<br>然后下载或git clone上面提到的Demo代码，打开安装好的微信开发者工具，用微信扫二维码登录，选择本地小程序项目 -&gt; 添加项目，导入项目，AppID可以不填，功能会受限。本来我还以为一定有微信小程序开发者帐号才能用呢？微信小程序开发者帐号的开通门槛还是比较高的，必须是企业帐号，还需要用对公帐号转账验证通过。现在看来只是功能受限。如下图</p>
<p><img src="http://upload-images.jianshu.io/upload_images/1387855-73940b5de1586900.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="小程序调试页面"></p>
<p>￼￼￼￼可以看到微信Web开发者工具主要有账户，编辑，调试，项目，刷新，编译，后台，缓存等几个功能，功能还是比较有限的，调试功能主要是整合了Chrome Web开发者工具。</p>
<h2 id="微信Web开发者工具是如何实现的"><a href="#微信Web开发者工具是如何实现的" class="headerlink" title="微信Web开发者工具是如何实现的"></a>微信Web开发者工具是如何实现的</h2><p>使用node.js和NW.js框架开发的，UI是基于NW.js框架开发的，支持Windows和Mac跨平台。这是很现实的技术选择，小程序的整个技术栈是基于H5的，虽然在UI框架层面自己实现了一套，但基础的js，css语法大部分还是支持的，因此开发者工具用node.js实现也是很正常的，现在我只是奇怪为什么微信没有选择用Atom Shell呢？就像Facebook的nuclide IDE那样，基于Atom编辑做个IDE？</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ul>
<li><a href="http://www.tuicool.com/articles/aAFZzmi" target="_blank" rel="external">使用 NodeJS 框架 NW.js 编写桌面应用入门</a></li>
<li><a href="https://github.com/RebeccaHanjw/weapp-wechat-zhihu" target="_blank" rel="external">微信中的知乎–微信小程序 demo</a></li>
</ul>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;微信小程序是当前的热点，市面上已经有很多微信小程序开发相关的文章，今天晚上抽了点时间折腾了微信小程序，并顺便看看了下微信小程序开发者工具的实现，是使用Node.js开发的，UI是基于NW.js框架开发的，支持Windows和Mac跨平台。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>我眼中的全栈工程师</title>
    <link href="http://offbye.com/2016/11/19/%E6%88%91%E7%90%86%E8%A7%A3%E7%9A%84%E5%85%A8%E6%A0%88%E5%B7%A5%E7%A8%8B%E5%B8%88%E6%98%AF%E6%80%8E%E6%A0%B7%E7%9A%84/"/>
    <id>http://offbye.com/2016/11/19/我理解的全栈工程师是怎样的/</id>
    <published>2016-11-19T15:02:37.000Z</published>
    <updated>2016-11-27T07:30:58.000Z</updated>
    
    <content type="html"><![CDATA[<p>现在越来越多的创业公司都想找全栈工程师，因此市场上就出现了很多伪全栈工程师，特别是学会了Node的前端工程师，前端工程师会Node就全栈了？这严重不符合我对全栈工程师的理解，这篇文章我主要是说下我对全栈工程师的个人看法。在我眼里，全栈工程师是下能玩硬件和驱动，上能写网页和js，中间能玩转服务器和数据库，没事还能自己做个安卓或iOS App， 对IT系统有着全面深刻的理解，熟悉所负责项目的整体技术栈。<br>我认为全栈工程师的特质应该有以下几个：强大的解决问题能力；广博的知识面，快速学习能力，不给自己设限，主动学习新技术；熟悉多种编程语言，熟悉整个系统从上到下的技术实现。<br><a id="more"></a><br><img src="http://upload-images.jianshu.io/upload_images/1387855-d439d0d02fe0e923.gif?imageMogr2/auto-orient/strip" alt="无人机之父拉菲罗·安德烈"></p>
<h2 id="强大的解决问题能力"><a href="#强大的解决问题能力" class="headerlink" title="强大的解决问题能力"></a>强大的解决问题能力</h2><p>我觉得强大的解决问题能力是全栈工程师最重要的素质，也是全栈工程师的核心能力。<br>全栈工程师的解决问题能力，应该不局限于软件研发中碰到的技术问题，范围可以扩展到工程和科学相关的问题。这一点我们要向国外的极客大神们学习，国外很多极客具备硬件，软件，网络和人工智能等领域的全方位能力，例如无人机领域的大神<a href="http://www.jiemian.com/article/848201.html" target="_blank" rel="external">拉菲罗·安德烈</a>。亚马逊运营中心里酷炫的、数以万计的Kiva机器人，就出自他手。公司被亚马逊收购后，这位“疯狂”的科技极客把注意力投向了无人机领域，三年时间研制出全球首个全向无人机。2015年获得“电子工程领域的诺贝尔奖”的IEEE机器人和自动化国际会议大奖，被人称为无人机之父。</p>
<p>在软件开发过程中所遇到的问题，真正的技术问题只占一部分，很多问题可能是工程或管理方面的问题。全栈工程师善于用全领域的知识积累从更本质的层面上找到解决问题的办法，而不是局限于某一种技术上寻求解决方案。例如前段时间遇到的HTML5 Hybrid App在某些安卓手机上兼容问题，最终的解决方案就是修改js代码，而是直接把Cordova的webview内核换成了腾讯X5内核，这个如果没有全栈技术能力是不可能做到的。</p>
<h2 id="快速学习能力，不给自己设限，主动学习新技术"><a href="#快速学习能力，不给自己设限，主动学习新技术" class="headerlink" title="快速学习能力，不给自己设限，主动学习新技术"></a>快速学习能力，不给自己设限，主动学习新技术</h2><p>全栈工程师应该时刻保持开放的心态，主动学习新技术，能够快速进入新技术领域。广博的知识面有助于通过类比和知识迁移的方式，提高学习掌握新技术的速度。<br>例如做Java Web后端可以去熟悉前端开发的技术。做安卓开发的可以去学习iOS开发。做iOS的可以去学习下HTML5移动开发，研究下js。<br>我们现在计算机体系其实都是相通的，不同的编程语言体系，不同的操作系统平台，同样的任务或技术架构的处理方式可能是类似的，因此全栈工程师<br>现在我们已经进入了移动互联网的下半场，未来的大趋势是人工智能，全栈工程师对于人工智能，机器学习，深度学习等领域也要尽早准备学习了。</p>
<h2 id="熟悉多种编程语言，熟悉整个系统从上到下的技术实现"><a href="#熟悉多种编程语言，熟悉整个系统从上到下的技术实现" class="headerlink" title="熟悉多种编程语言，熟悉整个系统从上到下的技术实现"></a>熟悉多种编程语言，熟悉整个系统从上到下的技术实现</h2><p>我个人认为全栈工程师至少要熟悉5种编程语言，工作中不能只用一种特定编程语言。<br>现在TIOBE排行榜上主流的编程语言Java, C/C++, C#, Python, Javascript, PHP,Ruby,OC等都应该熟悉，Groovy,Scala,Go,Swift,Kotlin等相对比较新的编程语言也应该熟悉或了解几种。这样就可以根据具体情况（项目特质和团队情况）选择合适的技术栈，而不是整个系统只能用一种语言完成。当然了，大的项目具体的代码肯定应该由团队分工合作完成，但全栈工程师还是应该对项目整个技术栈具备一定的掌控力。<br>做Java Web应用的全栈工程师，应该熟悉Java,Javascipt,CSS,HTML,SQL,XML等常用语言，并且熟悉常用的前后端技术框架，例如Spring，JPA，Mybatis，Jquery，AngularJs，React等。用PHP，Python，Ruby，微软.net等技术栈做Web应用的应该也类似。<br>做移动互联网应用的全栈工程师，应该熟悉Android，iOS开发或微信开发，以及服务器端接口的开发，这样才能做到沟通无障碍。</p>
<h2 id="全栈工程师的钱景和前景"><a href="#全栈工程师的钱景和前景" class="headerlink" title="全栈工程师的钱景和前景"></a>全栈工程师的钱景和前景</h2><p>成为一名真正的全栈工程师需要经过长期的持续学习和不断折腾，其中的甘苦只有自己知道。但和数年坚持一个领域的专家型人才相比，在薪酬方面全栈工程师可能并没有优势，这是可以理解的，因为在就业市场上，特别是像BAT这些比较大的公司，倾向于给行业顶级水平的人才很高的报酬，这符合赢者通吃的社会法则。而全栈工程师，由于关注和学习的领域太多，有限的精力必然被分散，导致在某个特定领域会比不上领域专家，因此在职业生涯早期和中期并不容易拿到足够高的收入。</p>
<p>从长期回报看，全栈工程师在架构师，CTO，技术合伙人等高端技术职务上会比领域技术专家更有优势，全面的技术背景有助于做出更加全面客观的技术架构和决策，从而对所在组织产生很大的正面影响。</p>
<p>对于创业公司，全栈工程师发挥空间会更大，多面手的特长可以帮助团队快速完成早期技术产品，成为公司创始人或技术合伙人，发展前景会很大。每一个全栈工程师，都有过一个创业梦。能否创业成功，在于是否有足够的勇气，综合能力和机遇。</p>
<p>真正的全栈工程师，对新技术有着强烈的好奇心，敢于不断地走出自己的舒适区，勇敢进入新的技术领域！</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;现在越来越多的创业公司都想找全栈工程师，因此市场上就出现了很多伪全栈工程师，特别是学会了Node的前端工程师，前端工程师会Node就全栈了？这严重不符合我对全栈工程师的理解，这篇文章我主要是说下我对全栈工程师的个人看法。在我眼里，全栈工程师是下能玩硬件和驱动，上能写网页和js，中间能玩转服务器和数据库，没事还能自己做个安卓或iOS App， 对IT系统有着全面深刻的理解，熟悉所负责项目的整体技术栈。&lt;br&gt;我认为全栈工程师的特质应该有以下几个：强大的解决问题能力；广博的知识面，快速学习能力，不给自己设限，主动学习新技术；熟悉多种编程语言，熟悉整个系统从上到下的技术实现。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>如何使用repo下载部分Android源码</title>
    <link href="http://offbye.com/2016/11/01/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8repo%E4%B8%8B%E8%BD%BD%E9%83%A8%E5%88%86Android%E6%BA%90%E7%A0%81/"/>
    <id>http://offbye.com/2016/11/01/如何使用repo下载部分Android源码/</id>
    <published>2016-11-01T14:19:34.000Z</published>
    <updated>2016-11-01T14:52:19.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近打算重新折腾下Android源码，由于网速慢和Mac的SSD磁盘空间不太够的原因，打算只下载部分想研究的Android源码看看，但又想保留android源码本身的结构，因此不能直接clone github上的android源码项目。目前android 7.0源码完整下载估计在40G左右，如果翻墙下载网速比较慢，可能几天都下不好，也就不要折腾了。</p>
<p>在分析了.repo目录下面的文件之后，我找到一个简单的方法，可以只下载部分源码的git项目，现在安卓源码一共由500多个git项目组成，一个人想了解全部源码基本上是不可能的，只能研究下重要的或者是工作涉及的部分。<br><a id="more"></a></p>
<p>由于是Mac上，需要按照官方的说明做些准备工作，具体参考<a href="http://source.android.com/source/initializing.html#setting-up-a-mac-os-x-build-environment" target="_blank" rel="external">http://source.android.com/source/initializing.html#setting-up-a-mac-os-x-build-environment</a><br>，具体做法如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 创建一个大小写敏感的磁盘镜像文件</span><br><span class="line">hdiutil create -type SPARSE -fs &apos;Case-sensitive Journaled HFS+&apos; -size 40g ~/android.dmg</span><br></pre></td></tr></table></figure>
<p>修改~/.bash_profile，加入2个函数<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># mount the android file image</span><br><span class="line">function mountAndroid &#123; hdiutil attach ~/android.dmg -mountpoint /Volumes/android; &#125;</span><br><span class="line"># unmount the android file image</span><br><span class="line">function umountAndroid() &#123; hdiutil detach /Volumes/android; &#125;</span><br></pre></td></tr></table></figure></p>
<p>运行<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">source ~/.bash_profile</span><br><span class="line"> mountAndroid   #挂载磁盘镜像到/Volumes/android</span><br><span class="line">cd  /Volumes/android</span><br></pre></td></tr></table></figure></p>
<p>因为本文不涉及到编译源码，所以编译环境相关但步骤就不介绍了，官方文档挺详细的。<br>下面介绍下repo吧</p>
<ol>
<li>repo是什么？</li>
</ol>
<p>repo是一种代码版本管理工具，它是由一系列的Python脚本组成，封装了一系列的Git命令，用来统一管理多个Git仓库。</p>
<ol>
<li>为什么要用repo？</li>
</ol>
<p>因为Android源码引用了很多开源项目，每一个子项目都是一个Git仓库，每个Git仓库都有很多分支版本，为了方便统一管理各个子项目的Git仓库，需要一个上层工具批量进行处理，因此repo诞生。</p>
<p>最后重点到了，看下.repo/manifests/default.xml文件，内容大概如下<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;</span><br><span class="line">&lt;manifest&gt;</span><br><span class="line"></span><br><span class="line">  &lt;remote  name=&quot;aosp&quot;</span><br><span class="line">           fetch=&quot;..&quot; /&gt;</span><br><span class="line">  &lt;default revision=&quot;refs/tags/android-7.0.0_r14&quot;</span><br><span class="line">           remote=&quot;aosp&quot;</span><br><span class="line">           sync-j=&quot;4&quot; /&gt;</span><br><span class="line"></span><br><span class="line">  &lt;project path=&quot;build&quot; name=&quot;platform/build&quot; groups=&quot;pdk,tradefed&quot; &gt;</span><br><span class="line">    &lt;copyfile src=&quot;core/root.mk&quot; dest=&quot;Makefile&quot; /&gt;</span><br><span class="line">  &lt;/project&gt;</span><br><span class="line">  &lt;project path=&quot;build/blueprint&quot; name=&quot;platform/build/blueprint&quot; groups=&quot;pdk,tradefed&quot; /&gt;</span><br><span class="line">  &lt;project path=&quot;build/kati&quot; name=&quot;platform/build/kati&quot; groups=&quot;pdk,tradefed&quot; /&gt;</span><br><span class="line">  &lt;!-- &lt;project path=&quot;build/soong&quot; name=&quot;platform/build/soong&quot; groups=&quot;pdk,tradefed&quot; &gt;</span><br><span class="line">    &lt;linkfile src=&quot;root.bp&quot; dest=&quot;Android.bp&quot; /&gt;</span><br><span class="line">    &lt;linkfile src=&quot;bootstrap.bash&quot; dest=&quot;bootstrap.bash&quot; /&gt;</span><br><span class="line">  &lt;/project&gt;</span><br><span class="line">  &lt;project path=&quot;abi/cpp&quot; name=&quot;platform/abi/cpp&quot; groups=&quot;pdk&quot; /&gt;</span><br><span class="line">  &lt;project path=&quot;art&quot; name=&quot;platform/art&quot; groups=&quot;pdk&quot; /&gt; --&gt;</span><br><span class="line">  ......</span><br><span class="line">  &lt;/manifest&gt;</span><br></pre></td></tr></table></figure></p>
<p>看到了吧，只要把不需要下载的源码project注释掉，然后运行repo sync就可以只下载没有被注释掉的项目了。当然这种办法只能用了研究源码，如果要编译源码，只下载一部分肯定是不行的。<br>repo sync后的项目结构如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">tree -L 1</span><br><span class="line">.</span><br><span class="line">├── CleanSpec.mk</span><br><span class="line">├── blueprint</span><br><span class="line">├── buildspec.mk.default</span><br><span class="line">├── core</span><br><span class="line">├── envsetup.sh</span><br><span class="line">├── kati</span><br><span class="line">├── libs</span><br><span class="line">├── target</span><br><span class="line">└── tools</span><br><span class="line"></span><br><span class="line">cd kati</span><br><span class="line">repo info .</span><br><span class="line">Manifest branch: refs/tags/android-7.0.0_r14</span><br><span class="line">Manifest merge branch: refs/heads/android-7.0.0_r14</span><br><span class="line">Manifest groups: all,-notdefault</span><br><span class="line">----------------------------</span><br><span class="line">Project: platform/build/kati</span><br><span class="line">Mount path: /Volumes/android/N/build/kati</span><br><span class="line">Current revision: refs/tags/android-7.0.0_r14</span><br><span class="line">Local Branches: 0</span><br></pre></td></tr></table></figure></p>
<p>这样我们就实现了用repo sync只下载想要的安卓源码项目的目的。</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近打算重新折腾下Android源码，由于网速慢和Mac的SSD磁盘空间不太够的原因，打算只下载部分想研究的Android源码看看，但又想保留android源码本身的结构，因此不能直接clone github上的android源码项目。目前android 7.0源码完整下载估计在40G左右，如果翻墙下载网速比较慢，可能几天都下不好，也就不要折腾了。&lt;/p&gt;
&lt;p&gt;在分析了.repo目录下面的文件之后，我找到一个简单的方法，可以只下载部分源码的git项目，现在安卓源码一共由500多个git项目组成，一个人想了解全部源码基本上是不可能的，只能研究下重要的或者是工作涉及的部分。&lt;br&gt;
    
    </summary>
    
    
      <category term="Android N" scheme="http://offbye.com/tags/Android-N/"/>
    
      <category term="源码" scheme="http://offbye.com/tags/%E6%BA%90%E7%A0%81/"/>
    
  </entry>
  
  <entry>
    <title>用Zepto代替Jquery提升移动Web用户体验</title>
    <link href="http://offbye.com/2016/10/18/%E7%94%A8Zepto%E4%BB%A3%E6%9B%BFJquery%E6%8F%90%E5%8D%87%E7%A7%BB%E5%8A%A8%E7%AB%AF%E7%94%A8%E6%88%B7%E4%BD%93%E9%AA%8C/"/>
    <id>http://offbye.com/2016/10/18/用Zepto代替Jquery提升移动端用户体验/</id>
    <published>2016-10-18T07:48:58.000Z</published>
    <updated>2016-10-19T01:50:33.000Z</updated>
    
    <content type="html"><![CDATA[<p>jQuery现在已经是Web开发者比较依赖的一个库.甚至在很多公司内部也把jQuery当成了一个基本库来使用.</p>
<p>但随着移动端的流行使用庞大的jQuery去支撑移动端的开发显得有些重量级了,于是就出现了一个轻量级兼容库Zepto.js.</p>
<p>Zepto是作为为了支持移动端的浏览器而开发的一个和jQuery API极度相似的一个库, 如果你会用jquery，那么你也会用zepto,这样就大大降低团队协作的成本,不用改变开发习惯也能迅速的进行移动端开发.<br><a id="more"></a></p>
<p>Zepto设计的目的是提供jquery的类似的APIs，但并不是100%覆盖jquery为目的。zepto设计的目的是有一个5-10k的通用库、下载并执行快、有一个熟悉通用的API，所以你能把你主要的精力放到应用开发上。</p>
<p>具体到我们的项目，由于各种原因，开始时并没有使用Zepto而是使用了Jquery。这轮性能优化打算换成Zepto，毕竟size查了10倍啊。但是简单的把js引入替换成zeptp显然是不行的。由于用了lazyload等插件，依赖了jQuery的$.Defered, 还用了css3的一些selector，因此还有需要引入Zepto的一些扩展模块，Zepto默认提供的压缩文件只包含几个默认模块，可以通过命令自定义打包其它模块，也可以通过<a href="http://github.e-sites.nl/zeptobuilder/进行在线自定义打包和压缩。" target="_blank" rel="external">http://github.e-sites.nl/zeptobuilder/进行在线自定义打包和压缩。</a></p>
<p>兼容Zepto的lazyload插件可以用这个<a href="https://github.com/jieyou/lazyload" target="_blank" rel="external">https://github.com/jieyou/lazyload</a></p>
<h2 id="Zepto-modules"><a href="#Zepto-modules" class="headerlink" title="Zepto modules"></a>Zepto modules</h2><p>Zepto modules are individual files in the “src/“ directory.</p>
<table><br><thead><tr><br>  <th>module</th> <th>default</th> <th>description</th><br></tr></thead><br><tbody><br>  <tr><br>    <th><a href="src/zepto.js#files">zepto</a></th><br>    <td>✔</td><br>    <td>Core module; contains most methods</td><br>  </tr><br>  <tr><br>    <th><a href="src/event.js#files">event</a></th><br>    <td>✔</td><br>    <td>Event handling via <code>on()</code> &amp; <code>off()</code></td><br>  </tr><br>  <tr><br>    <th><a href="src/ajax.js#files">ajax</a></th><br>    <td>✔</td><br>    <td>XMLHttpRequest and JSONP functionality</td><br>  </tr><br>  <tr><br>    <th><a href="src/form.js#files">form</a></th><br>    <td>✔</td><br>    <td>Serialize &amp; submit web forms</td><br>  </tr><br>  <tr><br>    <th><a href="src/ie.js#files">ie</a></th><br>    <td>✔</td><br>    <td>Support for Internet Explorer 10+ on the desktop and Windows Phone 8</td><br>  </tr><br>  <tr><br>    <th><a href="src/detect.js#files">detect</a></th><br>    <td></td><br>    <td>Provides <code>$.os</code> and <code>$.browser</code> information</td><br>  </tr><br>  <tr><br>    <th><a href="src/fx.js#files">fx</a></th><br>    <td></td><br>    <td>The <code>animate()</code> method</td><br>  </tr><br>  <tr><br>    <th><a href="src/fx_methods.js#files">fx_methods</a></th><br>    <td></td><br>    <td><br>      Animated <code>show</code>, <code>hide</code>, <code>toggle</code>,<br>      and <code>fade*()</code> methods.<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/assets.js#files">assets</a></th><br>    <td></td><br>    <td><br>      Experimental support for cleaning up iOS memory after removing<br>      image elements from the DOM.<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/data.js#files">data</a></th><br>    <td></td><br>    <td><br>      A full-blown <code>data()</code> method, capable of storing arbitrary<br>      objects in memory.<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/deferred.js#files">deferred</a></th><br>    <td></td><br>    <td><br>      Provides <code>$.Deferred</code> promises API.<br>      Depends on the “callbacks” module.<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/callbacks.js#files">callbacks</a></th><br>    <td></td><br>    <td><br>      Provides <code>$.Callbacks</code> for use in “deferred” module.<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/selector.js#files">selector</a></th><br>    <td></td><br>    <td><br>      Experimental <a href="http://api.jquery.com/category/selectors/jquery-selector-extensions/" target="_blank" rel="external">jQuery<br>      CSS extensions</a> support for functionality such as <code>$(‘div:first’)</code> and<br>      <code>el.is(‘:visible’)</code>.<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/touch.js#files">touch</a></th><br>    <td></td><br>    <td><br>      Fires tap– and swipe–related events on touch devices. This works with both<br>      <code>touch</code> (iOS, Android) and <code>pointer</code> events (Windows Phone).<br>    </td><br>  </tr><br>  <tr><br>    <th><a href="src/gesture.js#files">gesture</a></th><br>    <td></td><br>    <td>Fires pinch gesture events on touch devices</td><br>  </tr><br>  <tr><br>    <th><a href="src/stack.js#files">stack</a></th><br>    <td></td><br>    <td>Provides <code>andSelf</code> &amp; <code>end()</code> chaining methods</td><br>  </tr><br>  <tr><br>    <th><a href="src/ios3.js#files">ios3</a></th><br>    <td></td><br>    <td><br>      String.prototype.trim and Array.prototype.reduce methods<br>      (if they are missing) for compatibility with iOS 3.x.<br>    </td><br>  </tr><br></tbody><br></table>


<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;jQuery现在已经是Web开发者比较依赖的一个库.甚至在很多公司内部也把jQuery当成了一个基本库来使用.&lt;/p&gt;
&lt;p&gt;但随着移动端的流行使用庞大的jQuery去支撑移动端的开发显得有些重量级了,于是就出现了一个轻量级兼容库Zepto.js.&lt;/p&gt;
&lt;p&gt;Zepto是作为为了支持移动端的浏览器而开发的一个和jQuery API极度相似的一个库, 如果你会用jquery，那么你也会用zepto,这样就大大降低团队协作的成本,不用改变开发习惯也能迅速的进行移动端开发.&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>PyCon2016上海站参会感想</title>
    <link href="http://offbye.com/2016/09/21/PyCon2016%E4%B8%8A%E6%B5%B7%E5%8F%82%E4%BC%9A%E6%84%9F%E6%83%B3/"/>
    <id>http://offbye.com/2016/09/21/PyCon2016上海参会感想/</id>
    <published>2016-09-21T01:04:23.000Z</published>
    <updated>2016-10-12T10:48:57.000Z</updated>
    
    <content type="html"><![CDATA[<p>2016年9月10日早上5点多做高铁去上海参加<a href="http://cn.pycon.org/2016/" target="_blank" rel="external">PyCon2016大会</a>，这是我第二次参加PyCon大会，上一次是2014年，我觉得这是一个比其它的技术大会有意思的大会。在拖了整整一个月后，今天终于抽时间把参会感想整理出来了。PyCon大会是Python语言社群全球性的盛会，PyConChina是由CPyUG(华蠎用户组)获得授权举办的中国PyCon年会。</p>
<p>本次大会的主题是Pythonic, C’est la vie!  法语：Pythonic，这就是生活！<br>这次大会很好的体现了这个主题，演讲的嘉宾来自不同的行业，而不仅仅是传统的软件和互联网行业。印象比较深刻的2场演讲是vn.py的作者介绍Python和vn.py在Quant量化投资领域中的运用，《基于Python的谷歌TensorFlow深度学习框架的介绍》，《用高魔的姿势调Python程序》。具体的日程和PPT<a href="http://cn.pycon.org/2016/shanghai.html" target="_blank" rel="external">在这里</a>。</p>
<p>根据大会一场演讲中互动环境的Python用户情况现场调查，现场只要不到30%的人专职做Python开发的，其它的参会者都是在工作部分用到或业余用到Python。这个应该和Python的适用场景有关， Python作为一个有20多年历史，有着特殊的缩进语法风格，历史上积累的大量第三方库，跨平台，开始时是作为脚本语言推广，现在已经发展成了通用编程语言，有着广泛的使用场景。我自己主要是在工作中的小项目上用Python，例如几个月前用Python做了Android和iOS App的持续集成系统，通过jenkins自动调用Python脚本生成App，H5下载网页和二维码，方便测试。另外，以前折腾树莓派也是Python，使用RPi.GPIO库控制硬件，写代码效率比用C语言高很多，适合折腾。后面有兴趣研究下TensorFlow深度学习框架。</p>
<p>还有一个演讲是介绍基于Python的ERP系统Odoo, 但我感觉ERP这种产品是企业的神经啊，需要贴合国情，而Odoo是主要是在欧洲使用的，不一定适合国内情况，虽然可以做扩展。基于Python对系统的维护也会带有一些问题，国内熟悉Python的人相对较少，并且也不一定愿意去做ERP，搞机器学习多高端啊。</p>
<p>总之，今年的PyCon2016大会上海站还是诚意满满的，分享的东西都比较有意思。</p>
<a id="more"></a>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;2016年9月10日早上5点多做高铁去上海参加&lt;a href=&quot;http://cn.pycon.org/2016/&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;PyCon2016大会&lt;/a&gt;，这是我第二次参加PyCon大会，上一次是2014年，我觉得这是一个比其它的技术大会有意思的大会。在拖了整整一个月后，今天终于抽时间把参会感想整理出来了。PyCon大会是Python语言社群全球性的盛会，PyConChina是由CPyUG(华蠎用户组)获得授权举办的中国PyCon年会。&lt;/p&gt;
&lt;p&gt;本次大会的主题是Pythonic, C’est la vie!  法语：Pythonic，这就是生活！&lt;br&gt;这次大会很好的体现了这个主题，演讲的嘉宾来自不同的行业，而不仅仅是传统的软件和互联网行业。印象比较深刻的2场演讲是vn.py的作者介绍Python和vn.py在Quant量化投资领域中的运用，《基于Python的谷歌TensorFlow深度学习框架的介绍》，《用高魔的姿势调Python程序》。具体的日程和PPT&lt;a href=&quot;http://cn.pycon.org/2016/shanghai.html&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;在这里&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;根据大会一场演讲中互动环境的Python用户情况现场调查，现场只要不到30%的人专职做Python开发的，其它的参会者都是在工作部分用到或业余用到Python。这个应该和Python的适用场景有关， Python作为一个有20多年历史，有着特殊的缩进语法风格，历史上积累的大量第三方库，跨平台，开始时是作为脚本语言推广，现在已经发展成了通用编程语言，有着广泛的使用场景。我自己主要是在工作中的小项目上用Python，例如几个月前用Python做了Android和iOS App的持续集成系统，通过jenkins自动调用Python脚本生成App，H5下载网页和二维码，方便测试。另外，以前折腾树莓派也是Python，使用RPi.GPIO库控制硬件，写代码效率比用C语言高很多，适合折腾。后面有兴趣研究下TensorFlow深度学习框架。&lt;/p&gt;
&lt;p&gt;还有一个演讲是介绍基于Python的ERP系统Odoo, 但我感觉ERP这种产品是企业的神经啊，需要贴合国情，而Odoo是主要是在欧洲使用的，不一定适合国内情况，虽然可以做扩展。基于Python对系统的维护也会带有一些问题，国内熟悉Python的人相对较少，并且也不一定愿意去做ERP，搞机器学习多高端啊。&lt;/p&gt;
&lt;p&gt;总之，今年的PyCon2016大会上海站还是诚意满满的，分享的东西都比较有意思。&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Cordova整合使用腾讯浏览服务X5内核</title>
    <link href="http://offbye.com/2016/09/14/Cordova%E6%95%B4%E5%90%88%E4%BD%BF%E7%94%A8%E8%85%BE%E8%AE%AF%E6%B5%8F%E8%A7%88%E6%9C%8D%E5%8A%A1X5%E5%86%85%E6%A0%B8/"/>
    <id>http://offbye.com/2016/09/14/Cordova整合使用腾讯浏览服务X5内核/</id>
    <published>2016-09-14T07:54:20.000Z</published>
    <updated>2016-09-16T13:59:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>在对基于Cordova的Android混合App的性能优化过程中，遇到了很多在不同安卓版本手机上的Bug，很多都是安卓不同版本的系统webview的差异导致的，有可能是安卓系统的问题，也有可能是手机厂商修改webkit带来的问题，这些问题从前端技术层面是很难解决的，修改和测试成本都非常高。因此我想通过使用统一webkit内核的大招来解决，目前有2个方案可选，使用Crosswalk内核或腾讯浏览服务X5内核。本文主要研究Cordova整合使用腾讯浏览服务X5内核。</p>
<a id="more"></a>
<h2 id="问题背景"><a href="#问题背景" class="headerlink" title="问题背景"></a>问题背景</h2><p>熟悉Cordova生态圈的朋友可能听说过Crosswalk，<a href="https://crosswalk-project.org/documentation/cordova.html" target="_blank" rel="external">Crosswalk</a>是Intel维护的Webkit开源项目，可以通过插件安装命令<code>cordova plugin add cordova-plugin-crosswalk-webview</code> 安装，它的缺点就是太大了，集成后apk会增加20M，安装后会占用50M空间，因此一般不推荐普通App使用Crosswalk。</p>
<p>然后我就找到了<a href="http://x5.tencent.com/index" target="_blank" rel="external">腾讯浏览服务X5</a>,它是直接使用微信/手机QQ/空间的X5浏览器内核的，SDK只有250k，因此可以放心使用。鉴于这几个产品国内广泛的装机量，用户覆盖方面不用担心，如果微信/手机QQ/空间都没有安装，会自动降级到系统浏览器内核，或提示用户安装X5内核。</p>
<h2 id="腾讯X5内核介绍"><a href="#腾讯X5内核介绍" class="headerlink" title="腾讯X5内核介绍"></a>腾讯X5内核介绍</h2><blockquote>
<p>X5SDK是通过调用微信/手机QQ/空间的X5内核，解决系统webview兼容性差、加载速度慢、功能缺陷等问题，开发接入便捷，大小只有253K，仅需几行代码，即可解决一切令开发者们头疼的问题，为用户提供最优秀的浏览体验。</p>
</blockquote>
<p>同时，QQ浏览器团队还将持续更新和优化X5内核，持续优化功能，并保证兼容各种web新特性。</p>
<p>其相对于系统webview，具有下述明显优势：</p>
<p>1) 速度快：相比系统webView的网页加载速度有近30%的提升。<br>2) 省流量：云端优化技术使流量节省20%<br>3) 更安全：24小时安全问题解决机制<br>4) 更稳定：经过亿级用户的使用考验，CRASH率0.15%<br>5) 集成强大的视频播放器，支持各种视频格式直接打开<br>6) 适屏排版、字体设置等浏览增强功能的提供<br>7) Html5更完整支持。<br>8) 无系统内核的碎片化问题，更少的兼容性问题</p>
<h2 id="Cordova整合腾讯X5内核"><a href="#Cordova整合腾讯X5内核" class="headerlink" title="Cordova整合腾讯X5内核"></a>Cordova整合腾讯X5内核</h2><p>腾讯浏览服务官方只提供了如何把系统Webview替换成X5的接入文档，并没有提供Cordova集成的方法，论坛里有人问这个问题也没有官方回复。现在项目里面用了很多Cordoca插件提供拍照，扫二维码，App支付宝支付等功能，因此就需要把cordova和x5结合起来。</p>
<p>Cordova框架现在已经很完善，Cordova的Web Engine也是基于接口开发的，因此我参考系统engine的实现，写了一个x5engine插件，解决了Cordova调用X5内核的问题。</p>
<p>后面我会把这部分功能做成了一个Cordova插件，方便大家使用,github地址<a href="https://github.com/offbye/cordova-plugin-x5engine-webview。" target="_blank" rel="external">https://github.com/offbye/cordova-plugin-x5engine-webview。</a></p>
<h2 id="遇到的问题"><a href="#遇到的问题" class="headerlink" title="遇到的问题"></a>遇到的问题</h2><ol>
<li><p>64位手机上加载包含x5 so文件的插件报错<br>`TBS:initX5Core – loadSucc: false; exception: java.lang.reflect.InvocationTargetException; cause: java.lang.UnsatisfiedLinkError: dlopen failed: “/data/data/com.tencent.mm/app_tbs/core_share/libmttwebview.so” is 32-bit instead of 64-bit</p>
<pre><code>`
</code></pre><p>解决办法是在armeabi增加一个32位的jni so， 随便弄个小一点的so加上就可以，如果已经用了其它的JNI so应该不会有这个错误。</p>
</li>
<li><p>X5内核不支持file://本地域url，但支持本地相对路径。</p>
</li>
</ol>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在对基于Cordova的Android混合App的性能优化过程中，遇到了很多在不同安卓版本手机上的Bug，很多都是安卓不同版本的系统webview的差异导致的，有可能是安卓系统的问题，也有可能是手机厂商修改webkit带来的问题，这些问题从前端技术层面是很难解决的，修改和测试成本都非常高。因此我想通过使用统一webkit内核的大招来解决，目前有2个方案可选，使用Crosswalk内核或腾讯浏览服务X5内核。本文主要研究Cordova整合使用腾讯浏览服务X5内核。&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Swift和Javascript ES6/ES7比较研究</title>
    <link href="http://offbye.com/2016/09/13/Swift%E5%92%8CJavascript-ES6-ES7%E6%AF%94%E8%BE%83%E7%A0%94%E7%A9%B6/"/>
    <id>http://offbye.com/2016/09/13/Swift和Javascript-ES6-ES7比较研究/</id>
    <published>2016-09-13T03:47:36.000Z</published>
    <updated>2016-09-13T04:08:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>古罗马著名学者塔西陀曾说：“要想认识自己，就要把自己同别人进行比较“。比较是认识事物的基础，是人类认识、区别和确定事物异同关系的最常用的思维方法。比较研究法现已被广泛运用于科学研究的各个领域。在语言学，教育学，文学等社会科学研究中，比较研究是一种重要的研究方法。<br>本文将比较研究下Swift和Javascript ES6/ES7这2种当前最新的编程语言，看看两者在函数式编程，面向对象，面向协议等编程范式，以及部分语法细节方面的差异。</p>
<a id="more"></a>
<h2 id="1-变量定义比较"><a href="#1-变量定义比较" class="headerlink" title="1. 变量定义比较"></a>1. 变量定义比较</h2><p>Swift用var定义变量，let定义常量，一般情况我会先把所有变量都用let定义，需要时再改成var。<br>Javascript ES6以前版本没有let，只有var， var不支持块级作用域，如果一个变量没有用var定义，则默认是全局变量。<br>Javascript ES6引入了let和const， let用来定义<strong>变量</strong>，const用来定义常量；let定义的变量支持块级作用域，var定义的不支持块级作用域，因此推荐用let定义变量。<br>由此可见，Swift的变量定义更友好合理，毕竟是新语言，没有历史包袱。</p>
<h2 id="2-类和继承"><a href="#2-类和继承" class="headerlink" title="2. 类和继承"></a>2. 类和继承</h2><p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;古罗马著名学者塔西陀曾说：“要想认识自己，就要把自己同别人进行比较“。比较是认识事物的基础，是人类认识、区别和确定事物异同关系的最常用的思维方法。比较研究法现已被广泛运用于科学研究的各个领域。在语言学，教育学，文学等社会科学研究中，比较研究是一种重要的研究方法。&lt;br&gt;本文将比较研究下Swift和Javascript ES6/ES7这2种当前最新的编程语言，看看两者在函数式编程，面向对象，面向协议等编程范式，以及部分语法细节方面的差异。&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Node.js中使用redis数据库的正确姿势</title>
    <link href="http://offbye.com/2016/09/06/%E5%9C%A8koa-js%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%BC%82%E6%AD%A5%E6%96%B9%E5%BC%8F%E6%93%8D%E4%BD%9Credis%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <id>http://offbye.com/2016/09/06/在koa-js中使用异步方式操作redis数据库/</id>
    <published>2016-09-06T11:28:58.000Z</published>
    <updated>2016-09-06T10:06:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>Redis是一个常用的Nosql数据库，一般用来代替Memcached做缓存服务，同时它也支持数据的持久化，有着比较广泛的应用场景。在Java中使用redis我们已经比较熟悉了，那么在node.js和koa.js框架中使用Redis的正确姿势是怎样的呢？<br><a id="more"></a></p>
<p>Redis 是完全开源免费的，遵守BSD协议，是一个高性能的key-value数据库。<br>Redis 与其他 key - value 缓存产品有以下三个特点：</p>
<ul>
<li>Redis支持数据的持久化，可以将内存中的数据保持在磁盘中，重启的时候可以再次加载进行使用。</li>
<li>Redis不仅仅支持简单的key-value类型的数据，同时还提供list，set，zset，hash等数据结构的存储。</li>
<li>Redis支持数据的备份，即master-slave模式的数据备份。</li>
</ul>
<p>Redis常用命令可以参考<a href="http://www.runoob.com/redis/redis-keys.html" target="_blank" rel="external">http://www.runoob.com/redis/redis-keys.html</a></p>
<p>Node.js已经有很多redis相关的库，我在npm.org上搜了下大概有十几个吧，其中经常使用的redis,co-redis。 由于我用koa做web框架，因此就直接用了koa-redis。这篇文章涉及koa.js，yield生成器和Promise相关的知识，需要先对这些概念有一定的认识。</p>
<p>下面介绍下redis和koa.js相关的操作吧，我是在Mac下操作的。</p>
<h2 id="1-安装redis，并启动客户端和服务器端"><a href="#1-安装redis，并启动客户端和服务器端" class="headerlink" title="1. 安装redis，并启动客户端和服务器端"></a>1. 安装redis，并启动客户端和服务器端</h2><p>  <code>brew install redis</code></p>
<ul>
<li><p>启动服务器端   <code>redis-server</code>   </p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">29322:C 06 Sep 17:39:25.109 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf</span><br><span class="line">29322:M 06 Sep 17:39:25.111 * Increased maximum number of open files to 10032 (it was originally set to 1024).</span><br><span class="line">                _._</span><br><span class="line">           _.-``__ &apos;&apos;-._</span><br><span class="line">      _.-``    `.  `_.  &apos;&apos;-._           Redis 3.0.6 (00000000/0) 64 bit</span><br><span class="line">  .-`` .-```.  ```\/    _.,_ &apos;&apos;-._</span><br><span class="line"> (    &apos;      ,       .-`  | `,    )     Running in standalone mode</span><br><span class="line"> |`-._`-...-` __...-.``-._|&apos;` _.-&apos;|     Port: 6379</span><br><span class="line"> |    `-._   `._    /     _.-&apos;    |     PID: 29322</span><br><span class="line">  `-._    `-._  `-./  _.-&apos;    _.-&apos;</span><br><span class="line"> |`-._`-._    `-.__.-&apos;    _.-&apos;_.-&apos;|</span><br><span class="line"> |    `-._`-._        _.-&apos;_.-&apos;    |           http://redis.io</span><br><span class="line">  `-._    `-._`-.__.-&apos;_.-&apos;    _.-&apos;</span><br><span class="line"> |`-._`-._    `-.__.-&apos;    _.-&apos;_.-&apos;|</span><br><span class="line"> |    `-._`-._        _.-&apos;_.-&apos;    |</span><br><span class="line">  `-._    `-._`-.__.-&apos;_.-&apos;    _.-&apos;</span><br><span class="line">      `-._    `-.__.-&apos;    _.-&apos;</span><br><span class="line">          `-._        _.-&apos;</span><br><span class="line">              `-.__.-&apos;</span><br><span class="line"></span><br><span class="line">29322:M 06 Sep 17:39:25.116 # Server started, Redis version 3.0.6</span><br><span class="line">29322:M 06 Sep 17:39:25.116 * The server is now ready to accept connections on port 6379</span><br></pre></td></tr></table></figure>
</li>
<li><p>启动客户端 <code>redis-cli</code></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:6379[1]&gt; select 0</span><br><span class="line">OK</span><br><span class="line">127.0.0.1:6379&gt; keys *</span><br><span class="line">(empty list or set)</span><br><span class="line">127.0.0.1:6379&gt;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="2-安装node-js和koa-js，node的安装这里就不讲了，通过brew-install就可以。"><a href="#2-安装node-js和koa-js，node的安装这里就不讲了，通过brew-install就可以。" class="headerlink" title="2. 安装node.js和koa.js，node的安装这里就不讲了，通过brew install就可以。"></a>2. 安装node.js和koa.js，node的安装这里就不讲了，通过brew install就可以。</h2><p>  <code>npm install koa redis koa-redis</code></p>
<p>  可以看到koa-redis已经依赖了co-redis, es6-promisify等库<br>  <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">`-- koa-redis@2.1.2</span><br><span class="line">  +-- co-redis@2.1.1</span><br><span class="line">  | `-- es6-promisify@4.1.0</span><br><span class="line">  |   `-- es6-promise@3.2.1</span><br><span class="line">  `-- hiredis@0.5.0</span><br><span class="line">    +-- bindings@1.2.1</span><br><span class="line">    `-- nan@2.4.0</span><br></pre></td></tr></table></figure></p>
<h2 id="3-koa-js操作redis数据"><a href="#3-koa-js操作redis数据" class="headerlink" title="3. koa.js操作redis数据"></a>3. koa.js操作redis数据</h2><p>  这块是本文重点，由于官方的文档和例子不太详细，不熟悉node的同学折腾起来会比较累，所以本文提供了一个比较完整的例子。具体代码里面注释已经写的比较清楚了。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> session = <span class="built_in">require</span>(<span class="string">'koa-generic-session'</span>);</span><br><span class="line"><span class="keyword">var</span> redisStore = <span class="built_in">require</span>(<span class="string">'koa-redis'</span>);</span><br><span class="line"><span class="keyword">var</span> koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">var</span> redis = <span class="built_in">require</span>(<span class="string">'redis'</span>);</span><br><span class="line"><span class="comment">// 注意: client默认是异步callback方式调用;</span></span><br><span class="line"><span class="comment">// store.client是经过了co-redis包装,返回Promise, 在koa里面用yield异步编程比较方便</span></span><br><span class="line"><span class="keyword">var</span> client = redis.createClient(<span class="number">6379</span>, <span class="string">"172.19.65.240"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> app = koa();</span><br><span class="line">app.keys = [<span class="string">'keys'</span>, <span class="string">'keykeys'</span>];</span><br><span class="line"><span class="comment">// var option=&#123;host: "172.19.65.240", db:1&#125;;</span></span><br><span class="line"><span class="keyword">var</span> options = &#123;<span class="attr">client</span>: client, <span class="attr">db</span>: <span class="number">1</span>&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> store = redisStore(options);</span><br><span class="line">app.use(session(&#123;</span><br><span class="line">  store: store</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span> *(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">switch</span> (<span class="keyword">this</span>.path) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/get'</span>:</span><br><span class="line">      get.call(<span class="keyword">this</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/testKV'</span>:</span><br><span class="line">      <span class="comment">// 保存key value</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">this</span>.query.adminId) &#123;</span><br><span class="line">        <span class="keyword">yield</span> store.client.set(<span class="string">"test1"</span>, <span class="keyword">this</span>.query.adminId);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//同步读取key value</span></span><br><span class="line">      <span class="keyword">this</span>.body = <span class="keyword">yield</span> store.client.get(<span class="string">"test1"</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/testHM'</span>:</span><br><span class="line">      <span class="comment">//操作hashmap</span></span><br><span class="line">      <span class="keyword">var</span> result = <span class="keyword">yield</span> store.client.hmset(<span class="string">"hosts"</span>, <span class="string">"mjr"</span>, <span class="string">"123"</span>, <span class="string">"another"</span>, <span class="string">"23"</span>, <span class="string">"home"</span>, <span class="string">"1234"</span>);</span><br><span class="line">      <span class="built_in">console</span>.log(result);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">var</span> obj = <span class="keyword">yield</span> store.client.hgetall(<span class="string">"hosts"</span>)</span><br><span class="line">      <span class="built_in">console</span>.dir(obj);</span><br><span class="line">      <span class="comment">//获取hashmap key的值</span></span><br><span class="line">      <span class="keyword">this</span>.body = <span class="keyword">yield</span> store.client.hget(<span class="string">"hosts"</span>, <span class="string">"home"</span>);</span><br><span class="line"></span><br><span class="line">      <span class="comment">//保存hashmap,使用默认的callback方式</span></span><br><span class="line">      <span class="comment">// client.hset("hash key", "hashtest 1", "some value", redis.print);</span></span><br><span class="line">      <span class="comment">// client.hset(["hash key", "hashtest 2", "some other value"], redis.print);</span></span><br><span class="line">      <span class="comment">// client.hmset("hosts", "mjr", "1", "another", "23", "home", "1234");</span></span><br><span class="line">      <span class="comment">// client.hmset(["key", "test keys 1", "test val 1", "test keys 2", "test val 2"], function (err, res) &#123;</span></span><br><span class="line">      <span class="comment">//   console.log(res);</span></span><br><span class="line">      <span class="comment">// &#125;);</span></span><br><span class="line"></span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/testSet'</span>:</span><br><span class="line">      <span class="comment">//保存set</span></span><br><span class="line">      <span class="keyword">var</span> key = <span class="string">"key1"</span>;</span><br><span class="line">      store.client.sadd(<span class="string">"key1"</span>, <span class="string">"v1"</span>);</span><br><span class="line">      store.client.sadd(<span class="string">"key1"</span>, <span class="string">"v2"</span>);</span><br><span class="line">      store.client.sadd(<span class="string">"key1"</span>, <span class="string">"v3"</span>);</span><br><span class="line"></span><br><span class="line">      <span class="comment">//读取set</span></span><br><span class="line">      store.client.multi()</span><br><span class="line">        .sismember(key, <span class="string">'v1'</span>)</span><br><span class="line">        .smembers(key)</span><br><span class="line">        .exec(<span class="function"><span class="keyword">function</span> (<span class="params">err, replies</span>) </span>&#123;</span><br><span class="line">          <span class="built_in">console</span>.log(<span class="string">"MULTI got "</span> + replies.length + <span class="string">" replies"</span>);</span><br><span class="line">          replies.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">reply, index</span>) </span>&#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">"Reply "</span> + index + <span class="string">": "</span> + reply.toString());</span><br><span class="line">          &#125;);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">      <span class="comment">//读取set</span></span><br><span class="line">      <span class="keyword">this</span>.body = <span class="keyword">yield</span> store.client.smembers(<span class="string">"key1"</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/testList'</span>:</span><br><span class="line">      <span class="comment">//保存list</span></span><br><span class="line">      store.client.rpush(<span class="string">"mylist"</span>, <span class="string">"bbb"</span>)</span><br><span class="line">      store.client.rpush(<span class="string">"mylist"</span>, <span class="string">"ccc"</span>)</span><br><span class="line">      store.client.lpush(<span class="string">"mylist"</span>, <span class="string">"aaa"</span>)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">this</span>.body = <span class="keyword">yield</span> store.client.rpop(<span class="string">"mylist"</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/remove'</span>:</span><br><span class="line">      remove.call(<span class="keyword">this</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">'/regenerate'</span>:</span><br><span class="line">      <span class="keyword">yield</span> regenerate.call(<span class="keyword">this</span>);</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">get</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> session = <span class="keyword">this</span>.session;</span><br><span class="line">  session.count = session.count || <span class="number">0</span>;</span><br><span class="line">  session.count++;</span><br><span class="line">  <span class="keyword">var</span> test = store.client.get(<span class="string">"test"</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(test);</span><br><span class="line">  <span class="keyword">this</span>.body = session.count;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">remove</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.session = <span class="literal">null</span>;</span><br><span class="line">  <span class="keyword">this</span>.body = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> *<span class="title">regenerate</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  get.call(<span class="keyword">this</span>);</span><br><span class="line">  <span class="keyword">yield</span> <span class="keyword">this</span>.regenerateSession();</span><br><span class="line">  get.call(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">8080</span>);</span><br></pre></td></tr></table></figure>
<p>关于在koa框架中使用redis就写这么多吧，其实还有发布-订阅等的用法，这里就不说了。</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Redis是一个常用的Nosql数据库，一般用来代替Memcached做缓存服务，同时它也支持数据的持久化，有着比较广泛的应用场景。在Java中使用redis我们已经比较熟悉了，那么在node.js和koa.js框架中使用Redis的正确姿势是怎样的呢？&lt;br&gt;
    
    </summary>
    
    
      <category term="koa" scheme="http://offbye.com/tags/koa/"/>
    
      <category term="node" scheme="http://offbye.com/tags/node/"/>
    
      <category term="redis" scheme="http://offbye.com/tags/redis/"/>
    
  </entry>
  
  <entry>
    <title>Cordova支付宝插件的那些坑</title>
    <link href="http://offbye.com/2016/08/26/Cordova%E6%94%AF%E4%BB%98%E5%AE%9D%E6%8F%92%E4%BB%B6%E7%9A%84%E9%82%A3%E4%BA%9B%E5%9D%91/"/>
    <id>http://offbye.com/2016/08/26/Cordova支付宝插件的那些坑/</id>
    <published>2016-08-26T07:53:14.000Z</published>
    <updated>2016-08-29T04:35:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近帮忙折腾Cordova App，因为是商城类App，需要接APP微信支付和支付宝支付，于是先到github上找相关的插件解决，微信支付很顺利的用插件解决了，支付宝插件就不那么顺利了，网上的几个插件要么过时了，要么实现不合理或不完整，最后还是自己改了个支付宝插件。<br><a id="more"></a></p>
<p>首先我找了star最多的插件，<a href="https://github.com/charleyw/cordova-plugin-alipay，" target="_blank" rel="external">https://github.com/charleyw/cordova-plugin-alipay，</a> 结果发现这个插件已经过时了，API和最新的<a href="https://doc.open.alipay.com/doc2/detail.htm?treeId=54&amp;articleId=104509&amp;docType=1" target="_blank" rel="external">支付宝SDK</a>对不上， 然后签名竟然是在客户端计算，导致安全问题和iOS需要增加很大的静态库，于是放弃这个了，充分说明了star多的项目不一定就能用。</p>
<p>然后我找到了一个更新时间比较近的项目，<a href="https://github.com/chenyuanchn/cordova-plugin-alipay，这个项目比较简单合理，服务器端负责生成payInfo字符串，客户端支付调用支付宝。问题是iOS上编译有错误，原因是有几个库没有加入到插件config.xml中，不熟悉iOS的人就郁闷了，我参考了[iOS接入支付宝](http://caoyudong.com/2016/01/03/iOS%E6%8E%A5%E5%85%A5%E6%94%AF%E4%BB%98%E5%AE%9D/)解决问题。" target="_blank" rel="external">https://github.com/chenyuanchn/cordova-plugin-alipay，这个项目比较简单合理，服务器端负责生成payInfo字符串，客户端支付调用支付宝。问题是iOS上编译有错误，原因是有几个库没有加入到插件config.xml中，不熟悉iOS的人就郁闷了，我参考了[iOS接入支付宝](http://caoyudong.com/2016/01/03/iOS%E6%8E%A5%E5%85%A5%E6%94%AF%E4%BB%98%E5%AE%9D/)解决问题。</a><br>这个库还有个问题是没有实现callback回调。</p>
<p>然后我fork了这个插件，并完善了上面提到的几个问题，放在git上了。需要的可以拿去用。地址如下<br><a href="https://github.com/offbye/cordova-plugin-alipay" target="_blank" rel="external">https://github.com/offbye/cordova-plugin-alipay</a></p>
<p>最后强烈建议支付宝维护个官方的Cordova插件吧，毕竟这是涉及到支付安全的产品啊。</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近帮忙折腾Cordova App，因为是商城类App，需要接APP微信支付和支付宝支付，于是先到github上找相关的插件解决，微信支付很顺利的用插件解决了，支付宝插件就不那么顺利了，网上的几个插件要么过时了，要么实现不合理或不完整，最后还是自己改了个支付宝插件。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Mac Terminal终端光标的快捷键操作</title>
    <link href="http://offbye.com/2016/08/18/Mac-Terminal%E7%BB%88%E7%AB%AF%E5%85%89%E6%A0%87%E7%9A%84%E5%BF%AB%E6%8D%B7%E9%94%AE%E6%93%8D%E4%BD%9C/"/>
    <id>http://offbye.com/2016/08/18/Mac-Terminal终端光标的快捷键操作/</id>
    <published>2016-08-18T07:45:11.000Z</published>
    <updated>2016-08-18T08:01:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>Mac Terminal终端和linux上终端光标的快捷键操作是一样的，都是来自Emacs这个神级的编辑器，由于我以前vim用的多，没怎么用过Emacs，所以就不习惯了。<br><a id="more"></a></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">常用的快捷键：</span><br><span class="line">Ctrl + d        删除一个字符，相当于通常的Delete键（命令行若无所有字符，则相当于exit；处理多行标准输入时也表示eof）</span><br><span class="line">Ctrl + h        退格删除一个字符，相当于通常的Backspace键</span><br><span class="line">Ctrl + u        删除光标之前到行首的字符</span><br><span class="line">Ctrl + k        删除光标之前到行尾的字符</span><br><span class="line">Ctrl + c        取消当前行输入的命令，相当于Ctrl + Break</span><br><span class="line">Ctrl + a        光标移动到行首（Ahead of line），相当于通常的Home键</span><br><span class="line">Ctrl + e        光标移动到行尾（End of line）</span><br><span class="line">Ctrl + f        光标向前(Forward)移动一个字符位置</span><br><span class="line">Ctrl + b        光标往回(Backward)移动一个字符位置</span><br><span class="line">Ctrl + l        清屏，相当于执行clear命令</span><br><span class="line">Ctrl + p        调出命令历史中的前一条（Previous）命令，相当于通常的上箭头</span><br><span class="line">Ctrl + n        调出命令历史中的下一条（Next）命令，相当于通常的上箭头</span><br><span class="line">Ctrl + r        显示：号提示，根据用户输入查找相关历史命令（reverse-i-search）</span><br><span class="line"></span><br><span class="line">次常用快捷键：</span><br><span class="line">Alt + f         光标向前（Forward）移动到下一个单词</span><br><span class="line">Alt + b         光标往回（Backward）移动到前一个单词</span><br><span class="line">Ctrl + w        删除从光标位置前到当前所处单词（Word）的开头</span><br><span class="line">Alt + d         删除从光标位置到当前所处单词的末尾</span><br><span class="line">Ctrl + y        粘贴最后一次被删除的单词</span><br></pre></td></tr></table></figure>
<p>以上就是快捷键了，在这里我只想揭露一些事情，这些快捷键都是emacs的快捷键！熟悉终端的同学应该知道，终端里可以设置快捷键的类型是vim还是emacs。当然了大多数的终端都用的emacs，因为啥自己想去吧。我估计原因是这些东西都是早期的C/C++大神写的，一脉相承下来的！<br>mac os x虽然是图形界面，但是chrome，xcode啥的也支持这些快捷键！</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Mac Terminal终端和linux上终端光标的快捷键操作是一样的，都是来自Emacs这个神级的编辑器，由于我以前vim用的多，没怎么用过Emacs，所以就不习惯了。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Tower中国用户适配版本--cTower手机地面站</title>
    <link href="http://offbye.com/2016/08/04/Tower%E4%B8%AD%E5%9B%BD%E7%94%A8%E6%88%B7%E9%80%82%E9%85%8D%E7%89%88%E6%9C%AC-cTower%E6%89%8B%E6%9C%BA%E5%9C%B0%E9%9D%A2%E7%AB%99/"/>
    <id>http://offbye.com/2016/08/04/Tower中国用户适配版本-cTower手机地面站/</id>
    <published>2016-08-04T01:22:32.000Z</published>
    <updated>2016-08-04T01:23:25.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近折腾无人机，发现官方的Tower手机地面站在很多国产手机上不可用，没有谷歌gms服务啊，于是花了几天自己改了一个版本，欢迎广大模友试用。<br><a id="more"></a></p>
<p>Tower is a Ground Control Station (GCS) Android app built atop <a href="https://github.com/dronekit/dronekit-android" target="_blank" rel="external">3DR Services</a>, for UAVs<br>running Ardupilot software.</p>
<h1 id="Tower中国用户适配版本–cTower手机地面站-by-offbye"><a href="#Tower中国用户适配版本–cTower手机地面站-by-offbye" class="headerlink" title="Tower中国用户适配版本–cTower手机地面站 by offbye"></a>Tower中国用户适配版本–cTower手机地面站 by offbye</h1><blockquote>
<p>3DR官方Tower手机地面站在国内不适用，国内多数手机没有谷歌服务和谷歌市场，因此没法安装3DR services服务，谷歌地图在国内由于众所周知的原因，也不好用。</p>
<p>因此我为了自己玩的开心，Fork了Tower项目并逐步改的让国内主流安卓手机能正常使用，希望能够造福魔友，也欢迎有能力的朋友一起开发完善，不欢迎商业闭源使用。</p>
<p>cTower在尽量保留Tower原版最新功能不变的基础上，针对国内手机用户进行适配和增强，版本号和Tower保持同步。</p>
</blockquote>
<h2 id="3-2-2-更新说明"><a href="#3-2-2-更新说明" class="headerlink" title="3.2.2 更新说明"></a>3.2.2 更新说明</h2><ol>
<li>cTower地面站，实现支持去掉谷歌gms服务的国产手机</li>
<li>去掉3DR服务检查 by [i]offbye[/i]</li>
<li>增加高德地图支持，并设为默认地图，可以设置切换谷歌地图</li>
<li>修改了应用ID，避免和原版Tower冲突</li>
<li>修改gradle配置，适配高德地图，解决GFW导致一些repo下载慢无法编译的问题</li>
<li><p>增强简体中文翻译</p>
<h3 id="下载cTower3-2-2版本"><a href="#下载cTower3-2-2版本" class="headerlink" title="下载cTower3.2.2版本"></a><a href="http://o79096vir.bkt.clouddn.com/ctower/cTower-v3.2.2-release.apk" target="_blank" rel="external">下载cTower3.2.2版本</a></h3><p><img src="http://o79096vir.bkt.clouddn.com/ctower/S60803-20125595.jpg" alt="ctower"></p>
</li>
</ol>
<p>下载Tower官方版本 <a href="https://play.google.com/store/apps/details?id=org.droidplanner.android" target="_blank" rel="external"><img src="https://developer.android.com/images/brand/en_app_rgb_wo_45.png" alt="Google Play Store"></a></p>
<h3 id="Usage-Guide"><a href="#Usage-Guide" class="headerlink" title="Usage Guide"></a>Usage Guide</h3><p>The <a href="https://github.com/DroidPlanner/droidplanner/wiki" target="_blank" rel="external">wiki</a> has some basic documentation (Help us by making it better!)</p>
<p>For help, visit the <a href="http://discuss.ardupilot.org/c/ground-control-software/tower" target="_blank" rel="external">Tower Discuss forum</a>.</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近折腾无人机，发现官方的Tower手机地面站在很多国产手机上不可用，没有谷歌gms服务啊，于是花了几天自己改了一个版本，欢迎广大模友试用。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>安卓旧项目使用Small框架插件化改造踩坑记</title>
    <link href="http://offbye.com/2016/07/22/%E5%AE%89%E5%8D%93%E6%97%A7%E9%A1%B9%E7%9B%AE%E4%BD%BF%E7%94%A8Small%E6%A1%86%E6%9E%B6%E8%BF%9B%E8%A1%8C%E6%8F%92%E4%BB%B6%E5%8C%96%E6%94%B9%E9%80%A0%E7%9A%84%E9%97%AE%E9%A2%98%E6%B1%87%E6%80%BB/"/>
    <id>http://offbye.com/2016/07/22/安卓旧项目使用Small框架进行插件化改造的问题汇总/</id>
    <published>2016-07-22T13:28:18.000Z</published>
    <updated>2016-08-03T04:08:05.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们团队把一个10万行安卓代码的旧项目（电商系统管理台App），使用Small框架做了插件化改造。把项目分成了10多个插件模块，解除了业务模块之间的代码耦合，为业务功能的快速迭代和多团队并行开发做好基础架构。迁移期间遇到了一些坑，但最后在大家的努力下基本得到了解决，也感谢Small的作者光亮对我们提出的issue的快速响应。</p>
<a id="more"></a>
<h2 id="Small的应用场景"><a href="#Small的应用场景" class="headerlink" title="Small的应用场景"></a>Small的应用场景</h2><p>开发时：让你完全透明的像开发普通工程一样完成插件开发<br>编译时：自动化的帮助你分离各个公共库、业务模块插件（插件仅保留自身的代码跟资源，达到最小化）<br>运行时：运用最少量的Hook无缝的将各个插件并入宿主，让代码跟资源完全融合，自由调用<br>所以撇开开发者不用关注的(2)来说，Small的目的就是要做到保证开发者「畅快开发」的体验，以及App使用者「顺畅使用」的体验。</p>
<p>插件化方案有很多，很难有统一的形态，各有各的适用场景。如果你需要分发你的插件给别人使用，Small无法满足；如果你想利用插件化拆分、重组自己的应用，我认为Small在这方面已经走在了前面，并且将不断完善，做到极致。</p>
<h2 id="插件化的工程（开发）结构"><a href="#插件化的工程（开发）结构" class="headerlink" title="插件化的工程（开发）结构"></a>插件化的工程（开发）结构</h2><p><a href="https://github.com/wequick/Small/issues/118" target="_blank" rel="external">关于工程（开发）结构的讨论</a><br>1.依赖管理：Small使用同一个ClassLoader加载不同的插件，因此不同插件中的依赖在运行时对其他插件和宿主都是可见的，所以依赖的管理一定要收敛，统一管理，避免各自引入。<br>2.插件之间的通信：插件的开发一般都是多team并行的，插件之间应避免直接调用来减少耦合<br>3.资源的使用：由于small的特性，runtime各插件的资源也是可以相互访问的，但在开发上需要组织和统一管理好，包括命名和安全的使用资源。</p>
<h2 id="老项目集成Small插件的官方建议"><a href="#老项目集成Small插件的官方建议" class="headerlink" title="老项目集成Small插件的官方建议"></a>老项目集成Small插件的官方建议</h2><h3 id="基本原则"><a href="#基本原则" class="headerlink" title="基本原则"></a>基本原则</h3><p>宿主中不要放业务逻辑。只做加载插件以及调起主插件的操作。</p>
<h3 id="重构步骤"><a href="#重构步骤" class="headerlink" title="重构步骤"></a>重构步骤</h3><ol>
<li>拆lib.<em> - 公共模块插件<br>把各个第三方库拆出来做成一个个`lib.</em>`插件模块，包括统计、地图、网络、图片等库。<br>把老项目积累的业务公共代码(utils)分离出来封装成一个lib.utils插件<br>把基础的样式、主题分离出来封装成一个lib.style插件</li>
<li>拆app.<em> - 业务模块插件<br>把业务模块拆成`app.</em><code>模块，他们可以依赖</code>lib.<em><code>模块，显示调用</code>lib.</em>`中的各个API<br>相对独立的业务模块先拆，比如“详情页”、“关于我们”，如果剩下的业务不好拆，先放一个插件里<br>如果都不好拆，先把全部业务做成一个app.main主插件</li>
</ol>
<h2 id="插件之间的通信"><a href="#插件之间的通信" class="headerlink" title="插件之间的通信"></a>插件之间的通信</h2><p><a href="https://github.com/wequick/Small/issues/120" target="_blank" rel="external">插件之间的通信/互调</a><br>目前的插件之间的通信仅限于Small.openUri()，用来调起其它插件的页面。</p>
<p>但有些时候，我们还是需要操作一下其它插件。 比如说在跳转到另一个插件的页面之前，需要查询一下对方插件的一个状态。<br>我觉得应该借助v4包的LocalBroadcastManager来实现</p>
<h2 id="老项目集成Small插件化遇到的坑"><a href="#老项目集成Small插件化遇到的坑" class="headerlink" title="老项目集成Small插件化遇到的坑"></a>老项目集成Small插件化遇到的坑</h2><ol>
<li><p>插件中通过startActivityForResult获取不到数据问题。如果启动的Activity是SingleTask启动模式，有这个问题。</p>
</li>
<li><p>插件lib2依赖插件lib1，导致同时依赖lib1和lib2的插件app1编译时出现找不到类的情况。</p>
</li>
<li><p>一个lib依赖另一个lib，buildLib时出现Duplicate package错误<br>一个lib依赖另一个lib，buildLib时出现下面的错误，目前还没有找到解决吧，clean工程和cleanLib都做了，但还不行，只好把依赖去掉了才行。原因未知</p>
</li>
<li><p>如果插件用到了百度地图等，需要把相关的功能放在一个插件下面，注意那些很大的.so是放在插件里面的</p>
</li>
<li><p>Small插件中是支持aar引用的，实际使用过了</p>
</li>
<li><p>Small的插件限制了包名，因此已有代码复制过来会有改变。特别要注意layout文件中自定义控件包名的改动，能够通过编译但运行时会找不到类。</p>
</li>
<li><p>通过assembleRelease单独编译一个插件出现下面的错误，但有的电脑能正常单独编译插件，原因未知，已经提issue。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">下午2:30:54: Executing external task &apos;assembleRelease&apos;...</span><br><span class="line">Configuration on demand is an incubating feature.</span><br><span class="line">Incremental java compilation is an incubating feature.</span><br><span class="line"></span><br><span class="line">FAILURE: Build failed with an exception.</span><br><span class="line"></span><br><span class="line">* What went wrong:</span><br><span class="line">A problem occurred configuring project &apos;:app.me&apos;.</span><br><span class="line">&gt; Could not find property &apos;android&apos; on project &apos;:app&apos;.</span><br><span class="line"></span><br><span class="line">* Try:</span><br><span class="line">Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.</span><br><span class="line"></span><br><span class="line">BUILD FAILED</span><br></pre></td></tr></table></figure>
</li>
<li><p>第三方SDK的meta，Service等需要在宿主App的manifest中声明，例如百度地图SDK的定位Service。</p>
</li>
<li>lib.utils删除资源后出现编译错误，解决办法是删除public.txt，重新编译lib和bundle</li>
<li>老代码一些业务上耦合比较紧的页面不容易拆开，可以作为一个模块存在，我们最后拆成了6个大模块。</li>
<li><p>目前1.0版本不支持gradle并行编译，在gradle.properties中打开<code>org.gradle.parallel=true</code> 编译会报错</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">  gradle  buildLib -q</span><br><span class="line">[Small] [Small] building library 1 of 2 - app (0x7f)</span><br><span class="line">[Small] [Small] building library 1 of 2 - app (0x7f)</span><br><span class="line">building library 2 of 2 - lib.utils (0x73)</span><br><span class="line"></span><br><span class="line">FAILURE: Build failed with an exception.</span><br><span class="line"></span><br><span class="line">* What went wrong:</span><br><span class="line">Execution failed for task &apos;:lib.utils:preBuild&apos;.</span><br><span class="line"> ./build-small/intermediates/small-pre-link/aar/app-D.txt (No such file or directory)</span><br></pre></td></tr></table></figure>
<p>参考<a href="http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects" target="_blank" rel="external">gradle文档</a><br>这个不是Bug，有依赖的工程肯定是不能并行编译的</p>
</li>
</ol>
<p><em>后面遇到问题会持续更新</em></p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://github.com/wequick/Small/issues" target="_blank" rel="external">https://github.com/wequick/Small/issues</a></p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;我们团队把一个10万行安卓代码的旧项目（电商系统管理台App），使用Small框架做了插件化改造。把项目分成了10多个插件模块，解除了业务模块之间的代码耦合，为业务功能的快速迭代和多团队并行开发做好基础架构。迁移期间遇到了一些坑，但最后在大家的努力下基本得到了解决，也感谢Small的作者光亮对我们提出的issue的快速响应。&lt;/p&gt;
    
    </summary>
    
    
      <category term="Android" scheme="http://offbye.com/tags/Android/"/>
    
      <category term="Small框架" scheme="http://offbye.com/tags/Small%E6%A1%86%E6%9E%B6/"/>
    
      <category term="插件化" scheme="http://offbye.com/tags/%E6%8F%92%E4%BB%B6%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>买无人机之前你需要知道的几件事</title>
    <link href="http://offbye.com/2016/07/13/%E5%85%A5%E6%89%8B%E6%97%A0%E4%BA%BA%E6%9C%BA%E4%B9%8B%E5%89%8D%E4%BD%A0%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E5%87%A0%E4%BB%B6%E4%BA%8B/"/>
    <id>http://offbye.com/2016/07/13/入手无人机之前你需要知道的几件事/</id>
    <published>2016-07-13T04:17:28.000Z</published>
    <updated>2016-08-26T11:27:48.000Z</updated>
    
    <content type="html"><![CDATA[<p>随着近几年航拍机行业的火热，大疆等知名无人机厂商开始变得家喻户晓。近期小米无人机的上市，让越来越多的人想拥有自己的无人机。<br>航拍高大上，炸机很危险。在具备必要的知识和能力前，请不要把无人机作为炫耀的玩具。这几年小白玩大疆精灵在闹市区飞，在机场附近飞，炸机导致事故或影响公共安全的事情还少吗？<br>本文就谈下新手入手航拍机前需要知道的一些事情。作为一个在5iMX玩了很多年的资深模友，我首先以5iMX的56字航模箴言开头吧</p>
<blockquote>
<p>航模爱好趣无穷，正确操纵是关键。莫把器材当玩具，忽视安全悔一生。<br>    新手好问勤练习，远离人群勿炫耀。天下模友一家人，共建美好新生活。</p>
</blockquote>
<a id="more"></a>
<p>首先说下无人机这个概念吧，无人机应该是指拥有自主飞行能力的飞行器，即可以不需要人干预的情况下自动飞行，大疆和零度无人机多数型号是可以设置航点自主飞行的。现在消费市场上所说的无人机很多都只能算是航拍机，本文的讨论对象也主要是航拍机。</p>
<p>玩无人机安全问题需要时刻放在第一位，不在闹市区和人群密集的地方，不在机场附近等禁飞区飞，不在高大建筑物附近飞，不在发生重大事件或灾害的现场飞，除非得到了相关部门的明确授权，否则可能会触发相关法律。个人精神状态不好的时候不能飞，无人机有故障隐患没排除时不能飞。</p>
<p>在了解了这些后你还想入手无人机的话请继续往下看。</p>
<h2 id="你为什么想买无人机？"><a href="#你为什么想买无人机？" class="headerlink" title="你为什么想买无人机？"></a>你为什么想买无人机？</h2><p>每个人在入手无人机前都有自己的需求和动机，有人是为了在朋友圈发图和炫耀，有人是为了新奇，有人是为了学习和了解相关的技术。这些都是常见也是正常的需求，还有人是为了用无人机送给戒指什么的，这种需求还是交给专业团队做吧，就不要自己买无人机了，太危险。<br>需要知道的是，自己组装无人机并不比买商业无人机便宜，一般用户就不要为了省钱自己组装无人机了。但组装无人机是一件很有乐趣和挑战的事情，需要投入大量的时间和金钱。</p>
<h2 id="无人机相关的法规"><a href="#无人机相关的法规" class="headerlink" title="无人机相关的法规"></a>无人机相关的法规</h2><p>目前无人机方面的规定是怎样的情况呢？原则上，超过一定重量，在视距外飞行的无人机每次飞行前都需要申报飞行计划。尽管不少人有驾驶执照，申请计划的不便还是让多数无人机处于“黑飞”之中。中国航空器拥有者及驾驶员协会（以下简称AOPA）牵头，多家企业联合开发的针对轻小无人机的监管系统Ucloud已经上线。中国民用航空局飞行标准司已经正式批准轻小型无人机监管系统“优云（U-Cloud）”实施运行。批准文件有效期两年：自颁发日期2016年3月4日起至2018年3月3日。<br>但这个规定现在很多地方并没有认真执行过，倒是AOPA在通过发无人机执照赚钱，航模圈里业余玩的航模爱好者很少有人会去办这个执照，但商业无人机领域好像挺认这个证书的，会花1-2万考个证书。具体参考下知乎<a href="https://www.zhihu.com/question/33263185" target="_blank" rel="external">如何评价AOPA及ASFC颁发的相关无人机证件？</a></p>
<h2 id="玩无人机需要的技术技能"><a href="#玩无人机需要的技术技能" class="headerlink" title="玩无人机需要的技术技能"></a>玩无人机需要的技术技能</h2><p>玩商业无人机对于技能的要求比较低，只需要熟悉遥控器的操作和功能，较好的心理素质，了解无人机飞行的基本原理，熟悉手机和电脑操作。无人机经常会升级更新固件的，当然你也可以选择不升级。<br>如果是你打算自己组装无人机，需要知道的东西就很多了。需要有机械，电子，空气动力学，计算机等方面的知识，还需要有很强的动手能力和各种工具，熟悉飞控，电机，电调，动力锂电池，接收机，遥控器，图传，各种传感器等部件，还要熟悉APM，Pixhawk，CC3D等飞控和相关地面站软件，就不展开说了，总之门槛还是比较高的。</p>
<h2 id="飞行场地问题"><a href="#飞行场地问题" class="headerlink" title="飞行场地问题"></a>飞行场地问题</h2><p>无人机特别是固定翼飞机对于飞行场地要求还是比较高的，需要较大面积的空旷场地，并且不能在机场等设施附近，我一般是在湖边的大草坪飞，并且不在周末人比较多的时候飞，一般早上人很少时晚会比较方便。大疆等无人机都有禁飞区的设定，GPS定位到在禁飞区内是无法起飞的，北京四环以内好像都是禁飞区。<br>如果你生活在城市中心，附近没有合适的场地，入手无人机就需要考虑了，使用的频率会非常低。</p>
<p>最后祝大家飞的开心！</p>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;随着近几年航拍机行业的火热，大疆等知名无人机厂商开始变得家喻户晓。近期小米无人机的上市，让越来越多的人想拥有自己的无人机。&lt;br&gt;航拍高大上，炸机很危险。在具备必要的知识和能力前，请不要把无人机作为炫耀的玩具。这几年小白玩大疆精灵在闹市区飞，在机场附近飞，炸机导致事故或影响公共安全的事情还少吗？&lt;br&gt;本文就谈下新手入手航拍机前需要知道的一些事情。作为一个在5iMX玩了很多年的资深模友，我首先以5iMX的56字航模箴言开头吧&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;航模爱好趣无穷，正确操纵是关键。莫把器材当玩具，忽视安全悔一生。&lt;br&gt;    新手好问勤练习，远离人群勿炫耀。天下模友一家人，共建美好新生活。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Android OkHttp3使用http2问题记录</title>
    <link href="http://offbye.com/2016/07/09/Android-OkHttp3%E4%BD%BF%E7%94%A8http2%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/"/>
    <id>http://offbye.com/2016/07/09/Android-OkHttp3使用http2问题记录/</id>
    <published>2016-07-09T01:52:52.000Z</published>
    <updated>2016-07-18T02:01:24.000Z</updated>
    
    <content type="html"><![CDATA[<p>Android Okhttp3使用http2.0协议的接口时，发现一个问题，打印错误Log <code>E/NativeCrypto: ssl=0xd25d4000 cert_verify_callback x509_store_ctx</code>,Google上没有查到相关的信息，毕竟现在http2用的还很少。经查看Android源码，排查发现这只是个LOG，不是错误，可以放心的用http2了。</p>
<a id="more"></a>
<p>测试http2发现android打印了下面的LOG</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">07-08 14:33:57.182 13670-13697/com.qianmi.shine I/System.out: [CDS]connect[shineapi2.qianmi.com/120.55.247.77:443] tm:20</span><br><span class="line">07-08 14:33:57.187 13670-13697/com.qianmi.shine E/Posix: [Posix_connect Debug]Process com.qianmi.shine :443</span><br><span class="line">07-08 14:33:57.251 13670-13697/com.qianmi.shine D/libc-netbsd: [getaddrinfo]: hostname=shineapi2.qianmi.com; servname=(null); cache_mode=(null), netid=0; mark=0</span><br><span class="line">07-08 14:33:57.252 13670-13697/com.qianmi.shine D/libc-netbsd: [getaddrinfo]: ai_addrlen=0; ai_canonname=(null); ai_flags=4; ai_family=0</span><br><span class="line">07-08 14:33:57.300 13670-13697/com.qianmi.shine E/NativeCrypto: ssl=0xd25d4000 cert_verify_callback x509_store_ctx=0xdec78080 arg=0x0</span><br><span class="line">07-08 14:33:57.301 13670-13697/com.qianmi.shine E/NativeCrypto: ssl=0xd25d4000 cert_verify_callback calling verifyCertificateChain authMethod=ECDHE_RSA</span><br><span class="line">07-08 14:33:57.431 13670-13697/com.qianmi.shine I/System.out: gba_cipher_suite:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256</span><br></pre></td></tr></table></figure>
<p>到github上找下相关的Android源码，<br><a href="https://github.com/ACSOP/android_libcore/blob/88e2ca00cc0c161d9aa3e512b8ae58b3091dbf87/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp" target="_blank" rel="external">https://github.com/ACSOP/android_libcore/blob/88e2ca00cc0c161d9aa3e512b8ae58b3091dbf87/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp</a><br><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Verify the X509 certificate via SSL_CTX_set_cert_verify_callback</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">static int cert_verify_callback(X509_STORE_CTX* x509_store_ctx, void* arg __attribute__ ((unused)))</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* Get the correct index to the SSLobject stored into X509_STORE_CTX. */</span></span><br><span class="line">    SSL* ssl = <span class="keyword">reinterpret_cast</span>&lt;SSL*&gt;(X509_STORE_CTX_get_ex_data(x509_store_ctx,</span><br><span class="line">            SSL_get_ex_data_X509_STORE_CTX_idx()));</span><br><span class="line">    JNI_TRACE(<span class="string">"ssl=%p cert_verify_callback x509_store_ctx=%p arg=%p"</span>, ssl, x509_store_ctx, arg);</span><br><span class="line"></span><br><span class="line">    AppData* appData = toAppData(ssl);</span><br><span class="line">    JNIEnv* env = appData-&gt;env;</span><br><span class="line">    <span class="keyword">if</span> (env == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        LOGE(<span class="string">"AppData-&gt;env missing in cert_verify_callback"</span>);</span><br><span class="line">        JNI_TRACE(<span class="string">"ssl=%p cert_verify_callback =&gt; 0"</span>, ssl);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    jobject sslHandshakeCallbacks = appData-&gt;sslHandshakeCallbacks;</span><br><span class="line"></span><br><span class="line">    jclass cls = env-&gt;GetObjectClass(sslHandshakeCallbacks);</span><br><span class="line">    jmethodID methodID</span><br><span class="line">        = env-&gt;GetMethodID(cls, <span class="string">"verifyCertificateChain"</span>, <span class="string">"([[BLjava/lang/String;)V"</span>);</span><br><span class="line"></span><br><span class="line">    jobjectArray objectArray = getCertificateBytes(env, x509_store_ctx-&gt;untrusted);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span>* authMethod = SSL_authentication_method(ssl);</span><br><span class="line">    JNI_TRACE(<span class="string">"ssl=%p cert_verify_callback calling verifyCertificateChain authMethod=%s"</span>,</span><br><span class="line">              ssl, authMethod);</span><br><span class="line">    jstring authMethodString = env-&gt;NewStringUTF(authMethod);</span><br><span class="line">    env-&gt;CallVoidMethod(sslHandshakeCallbacks, methodID, objectArray, authMethodString);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> result = (env-&gt;ExceptionCheck()) ? <span class="number">0</span> : <span class="number">1</span>;</span><br><span class="line">    JNI_TRACE(<span class="string">"ssl=%p cert_verify_callback =&gt; %d"</span>, ssl, result);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>JNI_TRACE其实就是个封装了LOG的宏，定义如下：</p>
<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> WITH_JNI_TRACE</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> JNI_TRACE(...) \</span></span><br><span class="line">        ((<span class="keyword">void</span>)LOG(LOG_INFO, LOG_TAG <span class="string">"-jni"</span>, __VA_ARGS__));     \</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">        ((void)printf("I/" LOG_TAG "-jni:"));         \</span></span><br><span class="line"><span class="comment">        ((void)printf(__VA_ARGS__));          \</span></span><br><span class="line"><span class="comment">        ((void)printf("\n"))</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> JNI_TRACE(...) ((void)0)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure>
<p>但是最后还是有一点疑惑，源码打印的LOG是info级别的，但手机上logcat打印出的error log，这也是我为什么会注意到的原因，目前只能认为是手机和源码的代码不同了。</p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Android Okhttp3使用http2.0协议的接口时，发现一个问题，打印错误Log &lt;code&gt;E/NativeCrypto: ssl=0xd25d4000 cert_verify_callback x509_store_ctx&lt;/code&gt;,Google上没有查到相关的信息，毕竟现在http2用的还很少。经查看Android源码，排查发现这只是个LOG，不是错误，可以放心的用http2了。&lt;/p&gt;
    
    </summary>
    
    
      <category term="http2" scheme="http://offbye.com/tags/http2/"/>
    
      <category term="Android" scheme="http://offbye.com/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android Volley整合升级OkHttp3.3问题</title>
    <link href="http://offbye.com/2016/07/07/Android-Volley%E6%95%B4%E5%90%88%E5%8D%87%E7%BA%A7OkHttp3-3%E9%97%AE%E9%A2%98/"/>
    <id>http://offbye.com/2016/07/07/Android-Volley整合升级OkHttp3-3问题/</id>
    <published>2016-07-07T04:38:57.000Z</published>
    <updated>2016-07-08T11:15:55.000Z</updated>
    
    <content type="html"><![CDATA[<p>我们以前的Android项目网络框架是基于Volley和OKHttp2。现在由于Aliyun SDK升级和整合React Native模块的原因，它们现在都是用了OkHttp3，因此需要统一升级到最新稳定版本OkHttp3.3。<br>参考了网上的一些资料，主要是有下面几个问题:首先是’org.apache.http’在Android6.0版本已经去掉了，因此需要用’org.apache.http.legacy’代替下。<br><a id="more"></a></p>
<p>OkHttp3Stack是参考了下面的Gist代码实现的，评论里面说不支持https。但是我实际测试过是支持https和http2的。<br><a href="https://gist.github.com/alashow/c96c09320899e4caa06b" target="_blank" rel="external">https://gist.github.com/alashow/c96c09320899e4caa06b</a></p>
<p>在app/gradle.settings的android块增加下面代码<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">useLibrary &apos;org.apache.http.legacy&apos;</span><br></pre></td></tr></table></figure></p>
<p>另外一种方法是不再使用org.apache.http相关的类，参考<a href="http://blog.csdn.net/qq_28656671/article/details/50608239" target="_blank" rel="external">http://blog.csdn.net/qq_28656671/article/details/50608239</a></p>
<p>最后提醒一下，Volley框架现在已经不再升级了，后面的新项目不建议使用了，推荐使用基于Retrofit和OkHttp3的请求框架，老项目升级成本有点高，就不折腾了。</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ul>
<li><a href="http://www.jianshu.com/p/b022eed6b427" target="_blank" rel="external">http://www.jianshu.com/p/b022eed6b427</a></li>
<li><a href="http://blog.csdn.net/qq_28656671/article/details/50608239" target="_blank" rel="external">http://blog.csdn.net/qq_28656671/article/details/50608239</a></li>
<li><a href="http://blog.csdn.net/android_ls/article/details/50493581" target="_blank" rel="external">http://blog.csdn.net/android_ls/article/details/50493581</a></li>
<li><a href="https://github.com/square/okhttp/blob/master/CHANGELOG.md" target="_blank" rel="external">https://github.com/square/okhttp/blob/master/CHANGELOG.md</a></li>
<li><a href="https://gist.github.com/alashow/c96c09320899e4caa06b" target="_blank" rel="external">https://gist.github.com/alashow/c96c09320899e4caa06b</a></li>
<li><a href="http://www.cnblogs.com/richiewang/p/5140040.html" target="_blank" rel="external">http://www.cnblogs.com/richiewang/p/5140040.html</a></li>
<li><a href="https://github.com/mcxiaoke/android-volley" target="_blank" rel="external">https://github.com/mcxiaoke/android-volley</a><br><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></li>
</ul>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;我们以前的Android项目网络框架是基于Volley和OKHttp2。现在由于Aliyun SDK升级和整合React Native模块的原因，它们现在都是用了OkHttp3，因此需要统一升级到最新稳定版本OkHttp3.3。&lt;br&gt;参考了网上的一些资料，主要是有下面几个问题:首先是’org.apache.http’在Android6.0版本已经去掉了，因此需要用’org.apache.http.legacy’代替下。&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>JWT安全验证常见疑问解答</title>
    <link href="http://offbye.com/2016/07/04/JWT%E5%AE%89%E5%85%A8%E9%AA%8C%E8%AF%81%E5%B8%B8%E8%A7%81%E7%96%91%E9%97%AE%E8%A7%A3%E7%AD%94/"/>
    <id>http://offbye.com/2016/07/04/JWT安全验证常见疑问解答/</id>
    <published>2016-07-04T05:49:14.000Z</published>
    <updated>2016-07-05T04:21:37.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近做基于<a href="http://samnewman.io/patterns/architectural/bff/" target="_blank" rel="external">BFF架构</a>的分布式移动端API接口的系统设计。工作过程中发现有些工程师对JWT安全验证的认识存在一些偏差，重复讲解实在太麻烦了，在这里把关于JWT常见的一些疑问统一回答下吧。</p>
<ol>
<li><p>什么是JWT？</p>
<p><a href="http://tools.ietf.org/html/rfc7519" target="_blank" rel="external">JSON Web Token (JWT)</a>是一种基于 token 的认证方案。<br>JSON Web Tokens are an open, industry standard <a href="https://tools.ietf.org/html/rfc7519" target="_blank" rel="external">RFC 7519</a> method for representing claims securely between two parties.<br>简单的说，JWT就是一种Token的编码算法，服务器端负责根据一个密码和算法生成Token，然后发给客户端，客户端只负责后面每次请求都在HTTP header里面带上这个Token，服务器负责验证这个Token是不是合法的，有没有过期等，并可以解析出subject和claim里面的数据。</p>
<p>注意JWT里面的数据是BASE64编码的，没有加密，因此不要放如敏感数据。</p>
<p>可以通过<a href="https://jwt.io/这个网站对JWT" target="_blank" rel="external">https://jwt.io/这个网站对JWT</a> Token进行解析。</p>
<a id="more"></a>
<p>一个JWT token 看起来是这样的:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo</span><br></pre></td></tr></table></figure>
<p>可以简化为下面这样的结构:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">base64url_encode(Header) + &apos;.&apos; + base64url_encode(Claims) + &apos;.&apos; + base64url_encode(Signature)</span><br></pre></td></tr></table></figure>
</li>
<li><p>为什么用JWT？</p>
<p>JWT只通过算法实现对Token合法性的验证，不依赖数据库，Memcached的等存储系统，因此可以做到跨服务器验证，只要密钥和算法相同，不同服务器程序生成的Token可以互相验证。</p>
</li>
<li><p>JWT Token需要持久化在Memcached中吗？<br>不应该这样做，这样就背离了JWT通过算法验证的初心。</p>
</li>
<li><p>在退出登录时怎样实现JWT Token失效呢？<br>退出登录， 只要客户端端把Token丢弃就可以了，服务器端不需要废弃Token。</p>
</li>
<li><p>怎样保持客户端长时间保持登录状态？</p>
<p>服务器端提供刷新Token的接口， 客户端负责按一定的逻辑刷新服务器Token。</p>
</li>
<li><p>服务器端是否应该从JWT中取出userid用于业务查询？</p>
<p>REST API是无状态的，意味着服务器端每次请求都是独立的，即不依赖以前请求的结果，因此也不应该依赖JWT token做业务查询， 应该在请求报文中单独加个userid 字段。<br>为了做用户水平越权的检查，可以在业务层判断传入的userid和从JWT token中解析出的userid是否一致， 有些业务可能会允许查不同用户的数据。</p>
</li>
<li><p>JWT 在Java项目中如何实现？</p>
<p>生成Token</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">String token = Jwts.builder().setSubject(userId)</span><br><span class="line">        .setExpiration(<span class="keyword">new</span> Date(System.currentTimeMillis() + Constant.TOKEN_EXP_TIME))</span><br><span class="line">        .claim(<span class="string">"roles"</span>, Constant.USER_TYPE_EMP).setIssuedAt(<span class="keyword">new</span> Date())</span><br><span class="line">        .signWith(SignatureAlgorithm.HS256, Constant.JWT_SECRET).compact();</span><br><span class="line">loginResponse.setToken(token);</span><br></pre></td></tr></table></figure>
<p>验证JWT Token</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"> <span class="keyword">final</span> String authHeader = request.getHeader(<span class="string">"Authorization"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (authHeader == <span class="keyword">null</span> || !authHeader.startsWith(<span class="string">"Bearer "</span>)) &#123;</span><br><span class="line">   log.debug(<span class="string">"no Authorization "</span>, e);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> String token = authHeader.substring(<span class="number">7</span>); <span class="comment">// The part after "Bearer "</span></span><br><span class="line">        log.debug(<span class="string">"token "</span> + token);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> Claims claims = Jwts.parser().setSigningKey(Constant.JWT_SECRET)</span><br><span class="line">                .parseClaimsJws(token).getBody();</span><br><span class="line">        log.debug(claims.toString());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123; <span class="comment">//包含超时，签名错误等异常</span></span><br><span class="line"></span><br><span class="line">        log.debug(<span class="string">"JWT Exception"</span>, e);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>注意客户端发送的Authorization HTTP HEADER格式是 “Bearer YOUR_JWT_TOKEN”,这是OAuth的规范规定的。</p>
</li>
</ol>
<p><a href="http://www.offbye.com" target="_blank" rel="external">本文独立博客地址</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近做基于&lt;a href=&quot;http://samnewman.io/patterns/architectural/bff/&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;BFF架构&lt;/a&gt;的分布式移动端API接口的系统设计。工作过程中发现有些工程师对JWT安全验证的认识存在一些偏差，重复讲解实在太麻烦了，在这里把关于JWT常见的一些疑问统一回答下吧。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;什么是JWT？&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://tools.ietf.org/html/rfc7519&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;JSON Web Token (JWT)&lt;/a&gt;是一种基于 token 的认证方案。&lt;br&gt;JSON Web Tokens are an open, industry standard &lt;a href=&quot;https://tools.ietf.org/html/rfc7519&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;RFC 7519&lt;/a&gt; method for representing claims securely between two parties.&lt;br&gt;简单的说，JWT就是一种Token的编码算法，服务器端负责根据一个密码和算法生成Token，然后发给客户端，客户端只负责后面每次请求都在HTTP header里面带上这个Token，服务器负责验证这个Token是不是合法的，有没有过期等，并可以解析出subject和claim里面的数据。&lt;/p&gt;
&lt;p&gt;注意JWT里面的数据是BASE64编码的，没有加密，因此不要放如敏感数据。&lt;/p&gt;
&lt;p&gt;可以通过&lt;a href=&quot;https://jwt.io/这个网站对JWT&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;https://jwt.io/这个网站对JWT&lt;/a&gt; Token进行解析。&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
</feed>
