<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Gloomyer</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://gloomyer.com/</id>
  <link href="https://gloomyer.com/" rel="alternate"/>
  <link href="https://gloomyer.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Gloomyer</rights>
  <subtitle>Android · 音视频 · 逆向技术博客</subtitle>
  <title>Gloomyer</title>
  <updated>2026-10-14T19:30:00.000Z</updated>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="差分升级" scheme="https://gloomyer.com/tags/%E5%B7%AE%E5%88%86%E5%8D%87%E7%BA%A7/"/>
    <category term="NDK" scheme="https://gloomyer.com/tags/NDK/"/>
    <category term="HDiffPatch" scheme="https://gloomyer.com/tags/HDiffPatch/"/>
    <content>
      <![CDATA[<p>17 年写过一套 bsdiff 的差分升级，最近又碰上了，这次的包 300 多 MB，直接 diff 出来的差分包快到全量的一半。<br>折腾了一圈，记录一下怎么把它压到 22%，以及怎么让手机合成不卡死。</p><span id="more"></span><h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><p>之前那套差分升级（<a href="/2017/06/26/difference_update_01/">上：apk 差分</a>、<a href="/2017/06/26/difference_update_02/">中：差分包合并</a>、<a href="/2019/10/12/android_diff_server_and_downloadmanager/">部署和下载</a>）放在当年的 30MB 小包上还挺好用。</p><p>这次手上的 App 是 300 多 MB 的大家伙，arm64 单架构包都有 320MB 左右。</p><p>产品想上差分升级，我先拿相邻两个版本直接跑了一下 hdiffz（HDiffPatch，比 bsdiff 快很多，差分也更小）。</p><p>结果：差分包 148MB，是全量的 46%。</p><p>两个相邻版本，改动没多少，省下的流量才一半。这显然不对劲。</p><h2 id="为什么直接-diff-APK-会这么大"><a href="#为什么直接-diff-APK-会这么大" class="headerlink" title="为什么直接 diff APK 会这么大"></a>为什么直接 diff APK 会这么大</h2><p>APK 本质就是个 zip。dex、so、xml 这些大部分都是 deflate 压缩存的。</p><p>deflate 有个特点：原文哪怕只改了几个字节，压缩后的字节流从那个位置往后基本全变。</p><p>我拿一个 so 试了一下，解压后两个版本只有 36 个字节不同，压缩后的数据却整段都对不上。</p><p>diff 工具看到的是压缩后的字节，自然找不到能复用的地方，只能当新数据整段塞进差分包。</p><h2 id="思路：先解压再-diff"><a href="#思路：先解压再-diff" class="headerlink" title="思路：先解压再 diff"></a>思路：先解压再 diff</h2><p>这个问题 Google 早就碰到过，Play 商店的增量更新用的 archive-patcher 就是干这个的：</p><ol><li>生成端：把新旧 APK 里所有 deflate 条目解压展开，对展开后的数据做 diff，同时记下每个条目当初是用什么参数压缩的。</li><li>合成端：同样把旧 APK 展开，打上 patch 得到新包的展开数据，再按记下的参数把每个条目重新压缩回去。</li></ol><p>重新压缩回去之后，得到的 APK 要和官方新包<strong>逐字节一致</strong>，这样 v2&#x2F;v3 签名原样有效，不用重签。</p><p>听起来很美，但是有个关键前提：</p><p><strong>deflate 必须能逐字节复现。</strong></p><p>同一段数据，换个 level、换个 memLevel、换个 zlib 版本，压出来的字节都可能不一样。复现不了，就只能退回去用压缩后的字节参与 diff。</p><h2 id="先验证-deflate-能不能复现"><a href="#先验证-deflate-能不能复现" class="headerlink" title="先验证 deflate 能不能复现"></a>先验证 deflate 能不能复现</h2><p>直接暴力试。对新包每个 deflate 条目，拿标准 zlib 的 raw deflate（windowBits&#x3D;-15）挨个试 level 和 memLevel，看哪个组合压出来和原数据一模一样：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> zlib</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">probe</span>(<span class="params">raw: <span class="built_in">bytes</span>, compressed: <span class="built_in">bytes</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;返回能逐字节复现的 (level, memLevel)，找不到返回 None&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">for</span> level <span class="keyword">in</span> (<span class="number">6</span>, <span class="number">9</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">7</span>, <span class="number">8</span>):</span><br><span class="line">        <span class="keyword">for</span> mem_level <span class="keyword">in</span> (<span class="number">8</span>, <span class="number">9</span>):</span><br><span class="line">            c = zlib.compressobj(level, zlib.DEFLATED, -<span class="number">15</span>, mem_level)</span><br><span class="line">            <span class="keyword">if</span> c.compress(raw) + c.flush() == compressed:</span><br><span class="line">                <span class="keyword">return</span> level, mem_level</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">None</span></span><br></pre></td></tr></table></figure><p>新包差不多 1.9 万个 deflate 条目，结果出乎意料的好：</p><ul><li>level 6 &#x2F; memLevel 8：7700 多个，解压后约 200MB</li><li>level 9 &#x2F; memLevel 8：1.1 万多个，约 13MB</li><li>level 6 &#x2F; memLevel 9：1 个</li></ul><p><strong>100% 都能复现。</strong> 打包工具基本就是 zlib 默认参数，一部分资源用了 9。</p><p>那就好办了，每个条目记下 (偏移, 解压长度, 压缩长度, level, memLevel) 一共 26 个字节，1.9 万条用 lzma 压完才 110KB，在差分包里可以忽略不计。</p><p>zip 头、central directory、签名块这些不解压，原样参与 diff。</p><h2 id="第一版：差分包-70MB"><a href="#第一版：差分包-70MB" class="headerlink" title="第一版：差分包 70MB"></a>第一版：差分包 70MB</h2><p>生成端流程：</p><ol><li>展开旧包：把每个 deflate 条目的压缩数据原地替换成解压后的内容，其余字节原样保留。300 多 MB 的包展开后 650MB 左右。</li><li>展开新包：同样处理，只展开能复现的条目，同时生成上面那张参数表。</li><li><code>hdiffz -m-6 -c-lzma2-9-64m 旧展开 新展开 out</code></li><li>参数表和 hdiff 数据拼成一个文件，完事。</li></ol><p>合成端反过来：展开旧包（结果是确定的，不需要额外信息）→ hpatchz → 按参数表逐个条目重新 deflate 写回原位。每个条目都核对一下压缩后的长度，对不上直接报错。</p><p>结果：</p><table><thead><tr><th>方案</th><th>差分包</th><th>占全量</th><th>还原后签名</th></tr></thead><tbody><tr><td>直接 hdiff 整个 APK</td><td>148MB</td><td>46%</td><td>✅</td></tr><tr><td>ApkDiffPatch 一类</td><td>72MB</td><td>22.5%</td><td>❌ 新包要先规范化再重签</td></tr><tr><td>先解压再 diff</td><td>70MB</td><td>21.8%</td><td>✅ 逐字节一致</td></tr></tbody></table><p>ApkDiffPatch 那类方案差分包也很小，但前提是新包打包时先过一遍它的规范化再签名，已经发出去的包用不了。我们这边不想动打包流程，只能选后者。</p><h2 id="70MB-里面都是什么"><a href="#70MB-里面都是什么" class="headerlink" title="70MB 里面都是什么"></a>70MB 里面都是什么</h2><p>把展开后的数据按类型分开估了一下：</p><ul><li>dex：200 多 MB，差分占了 62MB 左右，大头里的大头</li><li>体积最大的那个业务 so：4.7MB</li><li>resources.arsc：4.1MB</li><li>其他 so：3.9MB</li><li>res、assets 和其他：1.7MB</li></ul><p>dex 两个版本之间大概有 22% 对不上。推测是混淆加固导致的，每次构建类名、方法名、字符串加密结果、类的排列顺序都可能整体变掉。</p><p>要再往下压只能动构建：混淆用 <code>-applymapping</code> 沿用上个版本的映射、字符串加密的密钥固定、或者对加固前的产物做 dex 级 diff（Tinker 那种）。这些都要动打包，先不搞了。</p><h2 id="问题来了：手机上合成太慢"><a href="#问题来了：手机上合成太慢" class="headerlink" title="问题来了：手机上合成太慢"></a>问题来了：手机上合成太慢</h2><p>第一版合成我是 Python 写的原型，在 Mac 上跑一次：<strong>20 秒、1.5GB 内存</strong>。</p><p>这放手机上就是找死。慢有三个原因：</p><ol><li>新包所有 deflate 条目都要重新压缩，600MB 左右的原始数据，单线程。</li><li>旧包、新包的展开文件整个读进内存。</li><li>hdiff 数据用的 lzma2 + 64MB 字典，光解压就要 160MB 内存。</li></ol><p>原则就一条：<strong>生成端可以慢、可以吃内存，手机端要快、内存要小。能提前算的全挪到生成端。</strong></p><h3 id="没变的条目直接拷贝"><a href="#没变的条目直接拷贝" class="headerlink" title="没变的条目直接拷贝"></a>没变的条目直接拷贝</h3><p>生成端先对旧包每个条目的压缩字节算个 hash，新包里压缩字节能在旧包找到的条目（包括改了名的），合成时直接从旧包拷原始字节，不解压也不重新压缩。</p><p>这样新 APK 被切成一串操作，按顺序拼起来正好是整个文件：</p><ul><li>COPY：从旧 APK 拷贝一段原始字节</li><li>DATA：原样写入（文件头、central directory、签名块、stored 条目、复现不了的 deflate 条目）</li><li>DEFLATE：拿到解压后的内容，按记录的 level&#x2F;memLevel 重新压缩后写入</li></ul><p>每个操作在新 APK 里的位置，等于前面所有操作输出长度之和，生成端和合成端算出来一样，所以可以多个线程直接 <code>pwrite</code> 到最终位置，谁也不用等谁。</p><p>实测下来，能直接拷贝的其实只有 80MB 左右，需要重新压缩的 dex 和 so 有 478MB，这部分绕不开，是合成耗时的下限。</p><h3 id="分组并行"><a href="#分组并行" class="headerlink" title="分组并行"></a>分组并行</h3><p>DATA 和 DEFLATE 分成 16 组，每组有自己的一段新数据流，生成端对每组单独做一次 hdiff。手机上每组一个线程，各算各的。</p><p>分组按合成代价做均衡（每次把最重的条目分给当前最闲的组），DEFLATE 按解压后字节数算，level ≥ 7 的乘个 1.6，DATA 只是写盘，按 5% 算。</p><p>组数故意比线程数多，大组先领，快核干完一组接着领下一组，大小核混搭的手机上负载更均匀。</p><h3 id="全程流式"><a href="#全程流式" class="headerlink" title="全程流式"></a>全程流式</h3><ul><li>旧包里没被 COPY 用掉的条目按顺序展开成一个临时文件，所有组共用，随机读。创建完立刻 unlink，崩溃了也不会留垃圾。</li><li>hpatch 顺序输出新数据，边输出边压缩，直接写到新 APK 的最终位置，不留中间文件。</li><li>hdiff 改成 <code>-SD</code> 单一压缩流 + zstd 1MB 窗口。窗口从 8MB 缩到 1MB，差分包只大了 0.4%，每组解压内存从几十 MB 降到 1MB 多一点。</li></ul><h3 id="zlib-自己带"><a href="#zlib-自己带" class="headerlink" title="zlib 自己带"></a>zlib 自己带</h3><p>这里有个坑：Android 系统自带的 zlib 不保证和标准 zlib 输出一致，不同系统版本、厂商可能有各自的优化，新版本 AOSP 据说用的是 Chromium 的 zlib 分支。</p><p>重新压缩要逐字节一致，系统 zlib 靠不住。干脆把标准 zlib 源码编进合成库，符号加 <code>-DZ_PREFIX</code> 前缀，免得和系统的 libz.so 打架。</p><p>合成库最后是纯 C 的：HDiffPatch 只用 <code>HPatch/patch.c</code>，zstd 只编解压部分，加上 zlib，arm64 strip 后 230KB 左右。</p><h2 id="第二版实测"><a href="#第二版实测" class="headerlink" title="第二版实测"></a>第二版实测</h2><p>代价是差分包从 70MB 涨到了 78MB 左右（zstd 压缩率不如 lzma2），换来的是：</p><table><thead><tr><th>测试环境</th><th>线程</th><th>耗时</th><th>内存峰值</th></tr></thead><tbody><tr><td>Mac，Python 第一版</td><td>1</td><td>20 秒</td><td>1.5GB</td></tr><tr><td>Pixel 旗舰，限定 4 个中核</td><td>4</td><td>6.6 秒</td><td>19MB</td></tr><tr><td>Pixel 旗舰，全部 8 核</td><td>8</td><td>5.0 秒</td><td>33MB</td></tr><tr><td>Pixel 旗舰，限定 2 个小核（模拟低端机）</td><td>2</td><td>30.5 秒</td><td>13MB</td></tr></tbody></table><p>每多一个线程只多 3MB 左右内存，所以线程数直接用 <code>Runtime.availableProcessors()</code>，上限 8。</p><p>2 个小核 30 秒是极端模拟，真实低端机一般也有 8 个核，按小核单线程每秒压 8MB 多估算，8 线程大概 10 秒，这个还得找真机验证。</p><p>另外拿 11 个历史旧版本往最新版本合成也跑了一遍：相邻版本 32% 左右，跨 6 个中版本也才 40%，还是能省一大半流量。合成耗时和内存跟版本跨度基本无关，因为每个版本 dex 和 so 都是整体在变，要重新压缩的数据量都在 500MB 上下。</p><h2 id="客户端接入要注意的"><a href="#客户端接入要注意的" class="headerlink" title="客户端接入要注意的"></a>客户端接入要注意的</h2><ul><li><strong>先校验旧包</strong>：差分包只能把某一个固定的旧包升上去。合成前对 <code>applicationInfo.sourceDir</code> 算 sha256，对不上就别合了。</li><li><strong>文件头放前面</strong>：差分包开头放一个固定长度、不压缩的头，带上旧包和新包的大小、sha256，可以用 HTTP Range 先下这一小段判断能不能用。</li><li><strong>检查空间</strong>：临时展开文件 + 新 APK，大概要 0.9GB，再留 10% 余量，不够就走全量。</li><li><strong>合成完必须校验新包 sha256</strong>，不一致删掉走全量。300 多 MB 的包算一遍 sha256 才 0.3 秒，没理由不做。</li><li><strong>任何一步失败都回退全量下载</strong>，并且把失败原因上报，灰度时重点盯”重新压缩长度不一致”这类错误。</li><li>放 WorkManager 里后台跑，装的时候走 PackageInstaller，签名还是原签名。</li></ul><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>还有一条路：打包时 dex 和 so 改成不压缩存储（AGP 的 <code>useLegacyPackaging = false</code>），合成时就几乎不用重新压缩了，模拟出来能快 3～4 倍。</p><p>但是全量包会大 85%，走全量下载的用户流量直接翻倍，还得确认加固工具支不支持。权衡了一下，没采用。</p><p>想自己试的话，HDiffPatch 在 <a href="https://github.com/sisong/HDiffPatch">GitHub</a> 上，原理可以看 Google 的 <a href="https://github.com/google/archive-patcher">archive-patcher</a>。</p>]]>
    </content>
    <id>https://gloomyer.com/2026/10/15/android-apk-delta-update/</id>
    <link href="https://gloomyer.com/2026/10/15/android-apk-delta-update/"/>
    <published>2026-10-14T19:30:00.000Z</published>
    <summary>直接对 APK 做 bsdiff/hdiff 差分包很大，是因为 deflate 压缩把改动放大了。参考 archive-patcher 先解压再 diff、合成时按原参数重新压缩，差分包从全量的 46% 降到 22%，合成出的 APK 与官方包逐字节一致；再把合成端搬到 NDK 上做流式、多线程，手机上 6 秒左右、内存 20MB。</summary>
    <title>Android 差分升级再折腾：先解压再 diff，差分包从 46% 压到 22%</title>
    <updated>2026-10-14T19:30:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="后端与运维" scheme="https://gloomyer.com/categories/%E5%90%8E%E7%AB%AF%E4%B8%8E%E8%BF%90%E7%BB%B4/"/>
    <category term="WireGuard" scheme="https://gloomyer.com/tags/WireGuard/"/>
    <category term="Docker" scheme="https://gloomyer.com/tags/Docker/"/>
    <category term="iptables" scheme="https://gloomyer.com/tags/iptables/"/>
    <category term="Rocky Linux" scheme="https://gloomyer.com/tags/Rocky-Linux/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>有一台家用 NAS，内部利用 Docker 启用了很多服务。NAS 物理机器通过 WireGuard 挂载到公网服务器上。</p><p>希望 NAS 内部的 Docker 服务能够通过公网访问。由于映射端口需要管理大量服务端口比较麻烦，所以直接通过 iptables 转发来实现。</p><hr><h2 id="基础架构"><a href="#基础架构" class="headerlink" title="基础架构"></a>基础架构</h2><p><strong>gateway（公网服务器）Docker 网络</strong></p><ul><li>nginx -&gt; 10.10.0.2  </li><li>wireguard-server -&gt; 10.10.0.3 &#x2F; WireGuard 网络网关 10.20.0.1</li></ul><p><strong>nas（家用服务器）</strong></p><ul><li>WireGuard（安装在物理机器上），获得的 IP 是 10.20.0.2  </li><li>Docker 网络  <ul><li>10.30.0.2 任意服务</li></ul></li></ul><p>现在希望能在 nginx 中访问到 10.30.0.2。</p><p>整体架构依托于 <strong>iptables + WireGuard</strong>，WireGuard 使用 <strong>wireguard-ui</strong> 管理。</p><hr><h2 id="详细实现"><a href="#详细实现" class="headerlink" title="详细实现"></a>详细实现</h2><blockquote><p>⚠️ <strong>物理机器务必开启 IP 转发！</strong></p></blockquote><h3 id="nginx"><a href="#nginx" class="headerlink" title="nginx"></a>nginx</h3><p>nginx 并不知道如何访问 <code>10.20.0.0/24</code> 和 <code>10.30.0.0/24</code> 网络，因此需要在 nginx 上做一些额外配置。</p><p>原始的 nginx 镜像不满足我的需求，我除了需要端口映射，还需要开启 stream 转发，所以自己编译了 nginx 镜像：</p><figure class="highlight dockerfile"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> debian:bookworm-slim</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 nginx 用户组和用户</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> groupadd --system --gid 101 nginx \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; useradd --system --gid nginx --no-create-home --home /nonexistent --comment <span class="string">&quot;nginx user&quot;</span> --uid 101 nginx</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装必要的工具和库</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; apt-get install --no-install-recommends --no-install-suggests -y \</span></span><br><span class="line"><span class="language-bash">    wget gcc g++ libpcre3 libpcre3-dev zlib1g zlib1g-dev openssl libssl-dev make \</span></span><br><span class="line"><span class="language-bash">    iputils-ping curl iproute2 \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; apt-get clean \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 下载并解压 Nginx 源码</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> wget http://nginx.org/download/nginx-1.28.0.tar.gz \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; tar -zxvf nginx-1.28.0.tar.gz \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; <span class="built_in">rm</span> nginx-1.28.0.tar.gz</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 编译并安装 Nginx</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">cd</span> nginx-1.28.0 \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; ./configure --prefix=/home/nginx --with-stream --with-http_ssl_module \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; make &amp;&amp; make install \</span></span><br><span class="line"><span class="language-bash">    &amp;&amp; <span class="built_in">mkdir</span> -p /home/nginx/confs</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 清理源码目录</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">rm</span> -rf nginx-1.28.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制自定义配置</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> nginx.conf /home/nginx/conf/nginx.conf</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建启动脚本</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> entrypoint.sh /entrypoint.sh</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> +x /entrypoint.sh</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><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"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;===================================&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Nginx Gateway Container Starting...&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;===================================&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加路由到 WireGuard 网络</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Adding route to 10.20.0.0/24 via 10.10.0.3...&quot;</span></span><br><span class="line">ip route add 10.20.0.0/24 via 10.10.0.3 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Adding route to 10.30.0.0/24 via 10.10.0.3...&quot;</span></span><br><span class="line">ip route add 10.30.0.0/24 via 10.10.0.3 2&gt;/dev/null || <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 显示当前路由表</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Current routing table:&quot;</span></span><br><span class="line">ip route</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Starting Nginx...&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;===================================&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">exec</span> /home/nginx/sbin/nginx -g <span class="string">&quot;daemon off;&quot;</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><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">docker rmi gateway-nginx:1.0.0</span><br><span class="line">docker run -i -d \</span><br><span class="line">  --restart=always \</span><br><span class="line">  --net gateway-v2 --ip 10.10.0.2 \</span><br><span class="line">  --name nginx \</span><br><span class="line">  --cap-add=NET_ADMIN \</span><br><span class="line">  -v /root/files/nginx/html:/home/nginx/html \</span><br><span class="line">  -v /root/files/nginx/logs:/home/nginx/logs \</span><br><span class="line">  -v /root/files/nginx/confs:/home/nginx/confs \</span><br><span class="line">  -p 80:80 \</span><br><span class="line">  gateway-nginx:1.0.0</span><br></pre></td></tr></table></figure><hr><h3 id="wireguard-server（wireguard-ui）"><a href="#wireguard-server（wireguard-ui）" class="headerlink" title="wireguard-server（wireguard-ui）"></a>wireguard-server（wireguard-ui）</h3><p>WireGuard 也需要知道要将 <code>10.30.0.0/24</code> 转发给 <code>10.20.0.2</code>，因此也需要进行一些配置。</p><p>这里仅展示核心部分（PostUp &amp; PostDown）：</p><figure class="highlight ini"><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="attr">PostUp</span> = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT<span class="comment">; iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT; ptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;  ip route add 10.30.0.0/24 via 10.20.0.2 dev wg0; wg set wg0 peer [peer,这里需要替换] allowed-ips 10.20.0.2/32,10.30.0.0/24</span></span><br><span class="line"></span><br><span class="line"><span class="attr">PreDown</span> =</span><br><span class="line"><span class="attr">PostDown</span> = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT<span class="comment">; iptables -D FORWARD -i eth0 -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip route del 10.30.0.0/24 via 10.20.0.2 dev wg0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><hr><h3 id="NAS-物理机器"><a href="#NAS-物理机器" class="headerlink" title="NAS 物理机器"></a>NAS 物理机器</h3><p>这里是最坑的部分。之前使用 Podman 时没问题，但 Podman 有其他限制，所以换回了 Docker。</p><p>Docker 默认有一些安全策略，不允许容器外 IP 直接访问容器（通过 iptables 实现）。如果直接关闭这个规则，会导致 WireGuard 无法正常连接。</p><p>因此，需要在 <strong>Docker 启动后再启动 WireGuard</strong>，并在 <code>PostUp</code> &#x2F; <code>PostDown</code> 中清理 Docker 的默认规则：</p><p>这里的br-4476e974a10f,是我创建的自定义网桥，需要替换成自己的.</p><figure class="highlight ini"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">PostUp</span> = iptables -t raw -F PREROUTING</span><br><span class="line"><span class="attr">PostUp</span> = iptables -F DOCKER</span><br><span class="line"><span class="attr">PostUp</span> = iptables -I DOCKER-USER <span class="number">1</span> -i wg0 -o br-<span class="number">4476</span>e974a10f -j ACCEPT</span><br><span class="line"><span class="attr">PostUp</span> = iptables -I DOCKER-USER <span class="number">1</span> -i wg0 -o docker0 -j ACCEPT</span><br><span class="line"><span class="attr">PostUp</span> = iptables -t nat -A POSTROUTING -s <span class="number">10.20</span>.<span class="number">0.0</span>/<span class="number">24</span> -o br-<span class="number">4476</span>e974a10f -j MASQUERADE</span><br><span class="line"></span><br><span class="line"><span class="attr">PostDown</span> = iptables -D DOCKER-USER -i %i -o br-<span class="number">4476</span>e974a10f -j ACCEPT</span><br><span class="line"><span class="attr">PostDown</span> = iptables -D DOCKER-USER -i %i -o docker0 -j ACCEPT</span><br><span class="line"><span class="attr">PostDown</span> = iptables -t nat -D POSTROUTING -s <span class="number">10.20</span>.<span class="number">0.0</span>/<span class="number">24</span> -o br-<span class="number">4476</span>e974a10f -j MASQUERADE</span><br></pre></td></tr></table></figure><p>同时，需要确保 WireGuard 在 Docker 之后启动，并且当 Docker 重启后，WireGuard 也能自动重启。</p><p>创建以下文件：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/etc/systemd/system/wg-quick@wg0.service.d/override.conf</span><br></pre></td></tr></table></figure><p>若目录不存在，请先创建。</p><p>内容如下：</p><figure class="highlight ini"><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 class="section">[Unit]</span></span><br><span class="line"><span class="attr">After</span>=docker.service network-<span class="literal">on</span>line.target</span><br><span class="line"><span class="attr">Wants</span>=docker.service</span><br><span class="line"><span class="attr">PartOf</span>=docker.service</span><br><span class="line"></span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">Restart</span>=<span class="literal">on</span>-failure</span><br><span class="line"><span class="attr">RestartSec</span>=<span class="number">5</span></span><br></pre></td></tr></table></figure><hr><p>至此，三层网络穿透已完成。</p>]]>
    </content>
    <id>https://gloomyer.com/2025/10/29/wireguard-into-docker-network/</id>
    <link href="https://gloomyer.com/2025/10/29/wireguard-into-docker-network/"/>
    <published>2025-10-28T18:49:53.000Z</published>
    <summary>有一台家用 NAS，内部利用 Docker 启用了很多服务。NAS 物理机器通过 WireGuard 挂载到公网服务器上。希望 NAS 内部的 Docker 服务能够通过公网访问。由于映射端口需要管理大量服务端口比较麻烦，所以直接通过 ip</summary>
    <title>如何配置 WireGuard 穿透 Docker 网络</title>
    <updated>2025-10-28T18:49:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="音视频" scheme="https://gloomyer.com/categories/%E9%9F%B3%E8%A7%86%E9%A2%91/"/>
    <category term="MediaCodec" scheme="https://gloomyer.com/tags/MediaCodec/"/>
    <category term="视频转码" scheme="https://gloomyer.com/tags/%E8%A7%86%E9%A2%91%E8%BD%AC%E7%A0%81/"/>
    <content>
      <![CDATA[<blockquote><p> 通过 android api MediaMuxer&amp;MediaExtractor 实现视频转格式</p><span id="more"></span></blockquote><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>最近app内需要上传视频,为了保证视频播放在各端的统一,需要将视频封装格式转换为mp4</p><p>本来上ffmpeg非常简单,但是ffmpeg对包体的增加会比较大,而且需求功能很简单 就考虑用Android的media相关api来实现.</p><p>流程上来说就是 未知视频格式-&gt;解码-&gt;编码-&gt;mp4</p><p>经测试 非h265&#x2F;h264 编码+mp4封装 ios支持 所以为了速度就不转码视频编码格式了</p><p>老视频中每读到一帧的数据 直接编码进新视频中就好了</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>MediaExtractor 一次只能解析一个轨道,所以需要创建两个,一个负责视频解码,一个负责音频解码</p><figure class="highlight kotlin"><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><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> android.media.MediaCodec</span><br><span class="line"><span class="keyword">import</span> android.media.MediaExtractor</span><br><span class="line"><span class="keyword">import</span> android.media.MediaFormat</span><br><span class="line"><span class="keyword">import</span> android.media.MediaMuxer</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.Dispatchers</span><br><span class="line"><span class="keyword">import</span> kotlinx.coroutines.withContext</span><br><span class="line"><span class="keyword">import</span> shop.memall.newapp.base.CM</span><br><span class="line"><span class="keyword">import</span> java.io.File</span><br><span class="line"><span class="keyword">import</span> java.lang.Exception</span><br><span class="line"><span class="keyword">import</span> java.lang.RuntimeException</span><br><span class="line"><span class="keyword">import</span> java.nio.ByteBuffer</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.atomic.AtomicInteger</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 视频转码工具</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Suppress(<span class="string">&quot;BlockingMethodInNonBlockingContext&quot;</span>)</span></span><br><span class="line"><span class="keyword">object</span> VideoTranscodingUtils &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 转码输入视频为mp4</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@JvmStatic</span></span><br><span class="line">    <span class="keyword">suspend</span> <span class="function"><span class="keyword">fun</span> <span class="title">transcoding2Mp4</span><span class="params">(origin: <span class="type">String</span>)</span></span>: String? &#123;</span><br><span class="line">        <span class="keyword">return</span> withContext(Dispatchers.IO) &#123;</span><br><span class="line">            <span class="keyword">val</span> dir = CM.mainApplication().getExternalFilesDir(<span class="string">&quot;transcodingVideos&quot;</span>)</span><br><span class="line">                ?: File(CM.mainApplication().cacheDir, <span class="string">&quot;transcodingVideos&quot;</span>)</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                dir.mkdirs()</span><br><span class="line">            &#125; <span class="keyword">catch</span> (ignore: Exception) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">val</span> output = File(dir, <span class="string">&quot;<span class="subst">$&#123;System.currentTimeMillis()&#125;</span>.mp4&quot;</span>)</span><br><span class="line">            <span class="keyword">var</span> mediaMuxer: MediaMuxer? = <span class="literal">null</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">val</span> videoExtractor = MediaExtractor()</span><br><span class="line">                <span class="keyword">val</span> audioExtractor = MediaExtractor()</span><br><span class="line">                mediaMuxer =</span><br><span class="line">                    MediaMuxer(output.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)</span><br><span class="line"></span><br><span class="line">                videoExtractor.setDataSource(origin)</span><br><span class="line">                audioExtractor.setDataSource(origin)</span><br><span class="line"></span><br><span class="line">                <span class="keyword">val</span> numTracks: <span class="built_in">Int</span> = videoExtractor.trackCount</span><br><span class="line">                <span class="keyword">var</span> muxerTrackNum = <span class="number">0</span></span><br><span class="line">                <span class="keyword">for</span> (idx <span class="keyword">in</span> <span class="number">0</span> until numTracks) &#123;</span><br><span class="line">                    <span class="keyword">val</span> trackFormat = videoExtractor.getTrackFormat(idx)</span><br><span class="line">                    <span class="keyword">val</span> mime = trackFormat.getString(MediaFormat.KEY_MIME) ?: <span class="string">&quot;&quot;</span></span><br><span class="line">                    <span class="keyword">if</span> (mime.contains(<span class="string">&quot;video&quot;</span>)) &#123;</span><br><span class="line">                        videoExtractor.selectTrack(idx)</span><br><span class="line">                        mediaMuxer.addTrack(trackFormat)</span><br><span class="line">                        muxerTrackNum++</span><br><span class="line">                    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (mime.contains(<span class="string">&quot;audio&quot;</span>)) &#123;</span><br><span class="line">                        audioExtractor.selectTrack(idx)</span><br><span class="line">                        mediaMuxer.addTrack(trackFormat)</span><br><span class="line">                        muxerTrackNum++</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">if</span> (muxerTrackNum &gt;= <span class="number">2</span>) <span class="keyword">break</span></span><br><span class="line">                    LG.fi(<span class="string">&quot;convert video track idx:<span class="variable">$idx</span> mime:<span class="variable">$mime</span>&quot;</span>)</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">val</span> atomicInteger = AtomicInteger(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">                mediaMuxer.start()</span><br><span class="line"></span><br><span class="line">                ThreadUtils.start(DecodeThread(videoExtractor, mediaMuxer, <span class="number">0</span>, atomicInteger))</span><br><span class="line">                ThreadUtils.start(DecodeThread(audioExtractor, mediaMuxer, <span class="number">1</span>, atomicInteger))</span><br><span class="line"></span><br><span class="line">                <span class="keyword">while</span> (atomicInteger.<span class="keyword">get</span>() &lt; <span class="number">2</span>) &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">0</span>)</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (atomicInteger.<span class="keyword">get</span>() &gt; <span class="number">2</span>) <span class="keyword">throw</span> RuntimeException(<span class="string">&quot;解码异常&quot;</span>);</span><br><span class="line">                output.absolutePath</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">                e.printStackTrace()</span><br><span class="line">                output.delete()</span><br><span class="line">                <span class="keyword">return</span><span class="symbol">@withContext</span> <span class="literal">null</span></span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                LG.fi(<span class="string">&quot;convert transcoding compile&quot;</span>)</span><br><span class="line">                mediaMuxer?.stop()</span><br><span class="line">                mediaMuxer?.release()</span><br><span class="line">            &#125;</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">/**</span></span><br><span class="line"><span class="comment">     * 解码线程</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">DecodeThread</span>(</span><br><span class="line">        <span class="keyword">val</span> extractor: MediaExtractor,</span><br><span class="line">        <span class="keyword">val</span> mediaMuxer: MediaMuxer,</span><br><span class="line">        <span class="keyword">val</span> model: <span class="built_in">Int</span>, <span class="comment">//0:视频轨道,1:音频轨道</span></span><br><span class="line">        <span class="keyword">val</span> atomicInteger: AtomicInteger</span><br><span class="line">    ) : Runnable &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">run</span><span class="params">()</span></span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">val</span> buffer = ByteBuffer.allocate(<span class="number">500</span> * <span class="number">1024</span>)</span><br><span class="line">                <span class="keyword">val</span> bufferInfo = MediaCodec.BufferInfo()</span><br><span class="line"></span><br><span class="line">                <span class="keyword">var</span> firstTime = <span class="number">0L</span></span><br><span class="line">                <span class="keyword">var</span> size: <span class="built_in">Int</span></span><br><span class="line"></span><br><span class="line">                <span class="keyword">while</span> (extractor.readSampleData(buffer, <span class="number">0</span>).also &#123; size = it &#125; &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">val</span> rackIndex = extractor.sampleTrackIndex</span><br><span class="line">                    <span class="keyword">if</span> (firstTime == <span class="number">0L</span>) firstTime = extractor.sampleTime</span><br><span class="line">                    bufferInfo.presentationTimeUs = extractor.sampleTime - firstTime</span><br><span class="line">                    bufferInfo.flags = extractor.sampleFlags</span><br><span class="line">                    bufferInfo.size = size</span><br><span class="line">                    mediaMuxer.writeSampleData(rackIndex, buffer, bufferInfo)</span><br><span class="line">                    extractor.advance()</span><br><span class="line">                &#125;</span><br><span class="line">                LG.fi(<span class="string">&quot;convert write success mode:<span class="variable">$model</span>&quot;</span>)</span><br><span class="line">                atomicInteger.addAndGet(<span class="number">1</span>)</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">                e.printStackTrace()</span><br><span class="line">                LG.fi(<span class="string">&quot;convert write has an exception mode:<span class="variable">$model</span>&quot;</span>)</span><br><span class="line">                atomicInteger.addAndGet(<span class="number">4</span>)</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                extractor.release()</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2021/07/12/android_api_convert_video_format/</id>
    <link href="https://gloomyer.com/2021/07/12/android_api_convert_video_format/"/>
    <published>2021-07-12T11:00:00.000Z</published>
    <summary>最近app内需要上传视频,为了保证视频播放在各端的统一,需要将视频封装格式转换为mp4，本来上ffmpeg非常简单,但是ffmpeg对包体的增加会比较大,而且需求功能很简单 就考虑用Android的media相关api来实现.</summary>
    <title>Android MediaMuxer + MediaExtractor 实现视频转格式</title>
    <updated>2021-07-12T11:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="前端与脚本" scheme="https://gloomyer.com/categories/%E5%89%8D%E7%AB%AF%E4%B8%8E%E8%84%9A%E6%9C%AC/"/>
    <category term="Python" scheme="https://gloomyer.com/tags/Python/"/>
    <category term="Selenium" scheme="https://gloomyer.com/tags/Selenium/"/>
    <category term="爬虫" scheme="https://gloomyer.com/tags/%E7%88%AC%E8%99%AB/"/>
    <content>
      <![CDATA[<blockquote><p>Python selenium 调用 隐藏指纹特征</p></blockquote><span id="more"></span><h3 id="为什么要隐藏"><a href="#为什么要隐藏" class="headerlink" title="为什么要隐藏?"></a>为什么要隐藏?</h3><p>很多网址做了大量的反扒行为 为了方便绕过这个检测 需要做指纹特征的隐藏</p><p><a href="https://bot.sannysoft.com/">检测当前浏览器指纹特征 https://bot.sannysoft.com/</a></p><h3 id="如何隐藏"><a href="#如何隐藏" class="headerlink" title="如何隐藏"></a>如何隐藏</h3><p>核心就是隐藏浏览器navigator.webdriver 这个对象</p><p>然后加上User-Agent等尽可能像是普通用户</p><p>再加上一个小技巧, 如果目标网站提供Mobile版本 通常爬Mobile站会容易许多..</p><p>上代码</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.chrome.options <span class="keyword">import</span> Options</span><br><span class="line"></span><br><span class="line">chrome_options = Options()</span><br><span class="line">chrome_options.add_argument(<span class="string">&quot;incognito&quot;</span>)</span><br><span class="line">chrome_options.add_argument(<span class="string">&quot;disable-extensions&quot;</span>)</span><br><span class="line">chrome_options.add_argument(<span class="string">&quot;disable-infobars&quot;</span>)</span><br><span class="line">chrome_options.add_argument(</span><br><span class="line">    <span class="string">&quot;user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) &quot;</span></span><br><span class="line">    <span class="string">&quot;Chrome/91.0.4472.124 Safari/537.36&quot;</span>)</span><br><span class="line">chrome_options.add_argument(<span class="string">&#x27;--disable-blink-features=AutomationControlled&#x27;</span>)</span><br><span class="line">chrome_options.add_experimental_option(<span class="string">&quot;excludeSwitches&quot;</span>, [<span class="string">&quot;enable-automation&quot;</span>])</span><br><span class="line">chrome_options.add_experimental_option(<span class="string">&#x27;useAutomationExtension&#x27;</span>, <span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">browser = webdriver.Chrome(executable_path=<span class="string">&quot;C:\\Users\\gloomy\\Downloads\\chromedriver_win32\\chromedriver.exe&quot;</span>,</span><br><span class="line">                           options=chrome_options)</span><br><span class="line">browser.execute_cdp_cmd(<span class="string">&quot;Page.addScriptToEvaluateOnNewDocument&quot;</span>, &#123;</span><br><span class="line">    <span class="string">&quot;source&quot;</span>: <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Object.defineProperty(navigator, &#x27;webdriver&#x27;, &#123;</span></span><br><span class="line"><span class="string">            get: () =&gt; undefined</span></span><br><span class="line"><span class="string">        &#125;)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">&#125;)</span><br><span class="line">browser.set_window_size(<span class="number">400</span>, <span class="number">785</span>)</span><br><span class="line">titleEl = browser.find_element_by_class_name(<span class="string">&quot;title-content&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(titleEl.text)</span><br><span class="line"><span class="built_in">print</span>(titleEl.text)</span><br><span class="line">browser.close()</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2021/06/28/python_webdriver_hide_fingerprint/</id>
    <link href="https://gloomyer.com/2021/06/28/python_webdriver_hide_fingerprint/"/>
    <published>2021-06-28T08:40:00.000Z</published>
    <summary>很多网址做了大量的反扒行为 为了方便绕过这个检测 需要做指纹特征的隐藏，检测当前浏览器指纹特征 https://bot.sannysoft.com/，核心就是隐藏浏览器navigator.webdriver 这个对象</summary>
    <title>Python Selenium 隐藏 WebDriver 指纹特征</title>
    <updated>2021-06-28T08:40:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="崩溃处理" scheme="https://gloomyer.com/tags/%E5%B4%A9%E6%BA%83%E5%A4%84%E7%90%86/"/>
    <category term="截屏" scheme="https://gloomyer.com/tags/%E6%88%AA%E5%B1%8F/"/>
    <content>
      <![CDATA[<blockquote><p>android app 如何录制行为录像  </p></blockquote><span id="more"></span><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><p>虽然app实现了捕捉崩溃日志的功能,但是在bug复现过程中 还是产生了不少的苦难</p><p>在接触h5之后, 知晓他们可以通过记录dom事件 来实现录像</p><p>突然感觉app实现录像也没有那么困难</p><p>视频本质就是图片的集合,我们是可以给自己的app实现截图功能的</p><p><font color='red'>开启录像后cpu 稳定会吃20%左右 所以切记只能在测试环境开启</font></p><h3 id="实现方案"><a href="#实现方案" class="headerlink" title="实现方案"></a>实现方案</h3><p>启动一个后台service 通过ActivityLifecycleCallbacks 获取栈顶activity, 然后走 drawingCache 获取界面截图</p><p>始终保存N（自定义）数量的图片路径　发生崩溃通过广播通知　录像管理器　来将之前缓存的图片封装成mp4视频</p><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><p>ErrorRecode.kt</p><figure class="highlight kotlin"><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"><span class="keyword">object</span> ErrorRecode &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> application: Application? = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//在 application 主线程中初始化一次</span></span><br><span class="line">    <span class="meta">@Synchronized</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">(app: <span class="type">Application</span>)</span></span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (application == <span class="literal">null</span>) &#123;</span><br><span class="line">            application = app</span><br><span class="line">            app.registerActivityLifecycleCallbacks(ActivityLifecycle)</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                app.startService(Intent(app, ErrorRecodeService::<span class="keyword">class</span>.java))</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>ActivityLifecycle.kt</p><figure class="highlight kotlin"><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 class="meta">@SuppressLint(<span class="string">&quot;StaticFieldLeak&quot;</span>)</span></span><br><span class="line"><span class="keyword">internal</span> <span class="keyword">object</span> ActivityLifecycle : Application.ActivityLifecycleCallbacks &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> resume = <span class="literal">false</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> currentActivity: Activity? = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">isResume</span><span class="params">()</span></span>: <span class="built_in">Boolean</span> = resume</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">getCurrentActivity</span><span class="params">()</span></span>: Activity? = currentActivity</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivityCreated</span><span class="params">(activity: <span class="type">Activity</span>, savedInstanceState: <span class="type">Bundle</span>?)</span></span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivityStarted</span><span class="params">(activity: <span class="type">Activity</span>)</span></span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivityResumed</span><span class="params">(activity: <span class="type">Activity</span>)</span></span> &#123;</span><br><span class="line">        resume = <span class="literal">true</span></span><br><span class="line">        currentActivity = activity</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivityPaused</span><span class="params">(activity: <span class="type">Activity</span>)</span></span> &#123;</span><br><span class="line">        resume = <span class="literal">false</span></span><br><span class="line">        <span class="keyword">if</span> (currentActivity == activity) currentActivity = <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivityStopped</span><span class="params">(activity: <span class="type">Activity</span>)</span></span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivitySaveInstanceState</span><span class="params">(activity: <span class="type">Activity</span>, outState: <span class="type">Bundle</span>)</span></span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onActivityDestroyed</span><span class="params">(activity: <span class="type">Activity</span>)</span></span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&#x2F;&#x2F;负责生成图片 和记录<br>ErrorRecodeService.kt</p><figure class="highlight kotlin"><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><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">class</span> <span class="title class_">ErrorRecodeService</span> : <span class="type">IntentService</span>(<span class="string">&quot;ErrorRecodeService&quot;</span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">val</span> MAX_FILE_LENGTH = <span class="number">5</span> * <span class="number">10</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">const</span> <span class="keyword">val</span> SLEEP_TIME = <span class="number">200L</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">val</span> fileList = ArrayList&lt;String?&gt;()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">object</span> ErrorRecodeReceiver : BroadcastReceiver() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onReceive</span><span class="params">(context: <span class="type">Context</span>, intent: <span class="type">Intent</span>)</span></span> &#123;</span><br><span class="line">            <span class="keyword">val</span> dataList = ArrayList&lt;String?&gt;()</span><br><span class="line">            <span class="comment">//错误日志监听广播接收者,崩溃统计方需要发广播通知这个接收者开始生成视频</span></span><br><span class="line">            Thread &#123;</span><br><span class="line">                synchronized(ErrorRecodeService::<span class="keyword">class</span>.java) &#123;</span><br><span class="line">                    dataList.addAll(fileList)</span><br><span class="line">                    fileList.clear()</span><br><span class="line">                &#125;</span><br><span class="line">                VideoUtils.store(context, dataList)</span><br><span class="line">            &#125;.start()</span><br><span class="line">            Log.i(<span class="string">&quot;ErrorRecodeService&quot;</span>, <span class="string">&quot;onReceive intent:<span class="variable">$dataList</span>&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> destroy = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onCreate</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate()</span><br><span class="line">        <span class="keyword">val</span> filter = IntentFilter(<span class="string">&quot;<span class="subst">$&#123;application.packageName&#125;</span>.error.recode.receiver&quot;</span>)</span><br><span class="line">        application.registerReceiver(ErrorRecodeReceiver, filter)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onDestroy</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">super</span>.onDestroy()</span><br><span class="line">        destroy = <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">onHandleIntent</span><span class="params">(intent: <span class="type">Intent</span>?)</span></span> &#123;</span><br><span class="line">        <span class="keyword">var</span> stateBarHeight = <span class="number">0</span></span><br><span class="line">        <span class="keyword">var</span> windowHeight = <span class="number">0</span></span><br><span class="line">        <span class="keyword">var</span> windowWidth = <span class="number">0</span></span><br><span class="line">        deleteCache()</span><br><span class="line">        Log.i(<span class="string">&quot;ErrorRecodeService&quot;</span>, <span class="string">&quot;onHandleIntent&quot;</span>)</span><br><span class="line">        <span class="keyword">while</span> (!destroy) &#123;</span><br><span class="line">            <span class="keyword">val</span> startTime = System.currentTimeMillis()</span><br><span class="line">            <span class="keyword">if</span> (ActivityLifecycle.isResume()) &#123;</span><br><span class="line">                <span class="keyword">val</span> activity = ActivityLifecycle.getCurrentActivity() ?: <span class="keyword">continue</span></span><br><span class="line">                <span class="keyword">val</span> decorView = activity.window?.decorView ?: <span class="keyword">continue</span></span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (windowHeight == <span class="number">0</span> || windowWidth == <span class="number">0</span> || stateBarHeight == <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="keyword">val</span> rect = Rect()</span><br><span class="line">                        decorView.getWindowVisibleDisplayFrame(rect)</span><br><span class="line">                        stateBarHeight = rect.top</span><br><span class="line">                        <span class="keyword">val</span> display = activity.windowManager.defaultDisplay</span><br><span class="line">                        windowWidth = display.width</span><br><span class="line">                        windowHeight = display.height</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line"></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">val</span> file = createFile(activity)</span><br><span class="line"></span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (windowHeight == <span class="number">0</span> || windowWidth == <span class="number">0</span> || stateBarHeight == <span class="number">0</span>) <span class="keyword">continue</span></span><br><span class="line">                    decorView.isDrawingCacheEnabled = <span class="literal">true</span></span><br><span class="line">                    <span class="keyword">val</span> bitmap =</span><br><span class="line">                        Bitmap.createBitmap(</span><br><span class="line">                            decorView.drawingCache,</span><br><span class="line">                            <span class="number">0</span>, stateBarHeight,</span><br><span class="line">                            windowWidth,</span><br><span class="line">                            windowHeight,</span><br><span class="line">                        )</span><br><span class="line">                    decorView.isDrawingCacheEnabled = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">                    <span class="keyword">var</span> fis: FileInputStream?</span><br><span class="line">                    <span class="keyword">var</span> fos: FileOutputStream?</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">val</span> ret =</span><br><span class="line">                        bitmap.compress(Bitmap.CompressFormat.JPEG, <span class="number">75</span>, FileOutputStream(file))</span><br><span class="line">                    <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">                        <span class="meta">@Suppress(<span class="string">&quot;RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS&quot;</span>)</span></span><br><span class="line">                        <span class="keyword">val</span> lbFile = Luban.with(applicationContext)</span><br><span class="line">                            .load(file)</span><br><span class="line">                            .ignoreBy(<span class="number">75</span>)</span><br><span class="line">                            .setTargetDir(file.parentFile.absolutePath)</span><br><span class="line">                            .<span class="keyword">get</span>()</span><br><span class="line">                        file.delete()</span><br><span class="line">                        <span class="keyword">if</span> (!lbFile.isNullOrEmpty()) &#123;</span><br><span class="line">                            fis = FileInputStream(lbFile[<span class="number">0</span>])</span><br><span class="line">                            fos = FileOutputStream(file)</span><br><span class="line">                            <span class="keyword">val</span> buffer = ByteArray(<span class="number">4096</span>)</span><br><span class="line">                            <span class="keyword">var</span> len: <span class="built_in">Int</span></span><br><span class="line">                            <span class="keyword">try</span> &#123;</span><br><span class="line">                                <span class="keyword">while</span> (fis.read(buffer).also &#123; len = it &#125; &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                                    <span class="keyword">for</span> (i <span class="keyword">in</span> <span class="number">0</span> until len) &#123;</span><br><span class="line">                                        buffer[i] = buffer[i].xor(<span class="number">0xF</span>)</span><br><span class="line">                                    &#125;</span><br><span class="line">                                    fos.write(buffer, <span class="number">0</span>, len)</span><br><span class="line">                                &#125;</span><br><span class="line"></span><br><span class="line">                                synchronized(ErrorRecodeService::<span class="keyword">class</span>.java) &#123;</span><br><span class="line">                                    <span class="keyword">if</span> (fileList.size &gt;= MAX_FILE_LENGTH) &#123;</span><br><span class="line">                                        application.sendBroadcast(</span><br><span class="line">                                            Intent(<span class="string">&quot;<span class="subst">$&#123;application.packageName&#125;</span>.error.recode.receiver&quot;</span>).putExtra(</span><br><span class="line">                                                <span class="string">&quot;error&quot;</span>,</span><br><span class="line">                                                <span class="string">&quot;error&quot;</span></span><br><span class="line">                                            )</span><br><span class="line">                                        )</span><br><span class="line">                                        <span class="keyword">val</span> delFile = fileList.removeAt(<span class="number">0</span>)</span><br><span class="line">                                        <span class="keyword">if</span> (delFile != <span class="literal">null</span>) File(delFile).delete()</span><br><span class="line">                                    &#125;</span><br><span class="line">                                    fileList.add(file.absolutePath)</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line">                            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                                <span class="keyword">if</span> (lbFile.size &gt; <span class="number">0</span> &amp;&amp; lbFile[<span class="number">0</span>] != <span class="literal">null</span>) lbFile[<span class="number">0</span>].delete()</span><br><span class="line">                                <span class="keyword">try</span> &#123;</span><br><span class="line">                                    fos.close()</span><br><span class="line">                                &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line">                                &#125;</span><br><span class="line">                                <span class="keyword">try</span> &#123;</span><br><span class="line">                                    fis.close()</span><br><span class="line">                                &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                        Log.i(<span class="string">&quot;ErrorRecodeService&quot;</span>, <span class="string">&quot;save once frame time: <span class="subst">$&#123;System.currentTimeMillis() - startTime&#125;</span> ms&quot;</span>)</span><br><span class="line">                        <span class="keyword">continue</span></span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">                    file.delete()</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            Thread.sleep(SLEEP_TIME)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">deleteCache</span><span class="params">()</span></span> &#123;</span><br><span class="line">        <span class="keyword">var</span> file = applicationContext.getExternalFilesDir(<span class="string">&quot;error_recode&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> (file == <span class="literal">null</span>) file = applicationContext.cacheDir!!</span><br><span class="line">        file = File(file, <span class="string">&quot;frames&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!file.exists() || file.isFile) file.mkdirs()</span><br><span class="line">        <span class="keyword">val</span> fileList = file.listFiles() ?: <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">for</span> (f <span class="keyword">in</span> fileList) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                f.delete()</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createFile</span><span class="params">(activity: <span class="type">Activity</span>)</span></span>: File &#123;</span><br><span class="line">        <span class="keyword">var</span> file = activity.getExternalFilesDir(<span class="string">&quot;error_recode&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> (file == <span class="literal">null</span>) file = activity.cacheDir!!</span><br><span class="line">        file = File(file, <span class="string">&quot;frames&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!file.exists() || file.isFile) file.mkdirs()</span><br><span class="line">        <span class="keyword">return</span> File(file, <span class="string">&quot;<span class="subst">$&#123;System.currentTimeMillis()&#125;</span>.frame&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>&#x2F;&#x2F;图片合成视频<br>VideoUtils.kt</p><figure class="highlight kotlin"><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><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">internal</span> <span class="keyword">object</span> VideoUtils &#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">getMediaCodecList</span><span class="params">()</span></span>: IntArray? &#123;</span><br><span class="line">        <span class="comment">//获取解码器列表</span></span><br><span class="line">        <span class="keyword">val</span> numCodecs = MediaCodecList.REGULAR_CODECS</span><br><span class="line">        <span class="keyword">var</span> codecInfo: MediaCodecInfo? = <span class="literal">null</span></span><br><span class="line">        <span class="keyword">var</span> i = <span class="number">0</span></span><br><span class="line">        <span class="keyword">while</span> (i &lt; numCodecs &amp;&amp; codecInfo == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">val</span> info = MediaCodecList.getCodecInfoAt(i)</span><br><span class="line">            <span class="keyword">if</span> (!info.isEncoder) &#123;</span><br><span class="line">                i++</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">val</span> types = info.supportedTypes</span><br><span class="line">            <span class="keyword">var</span> found = <span class="literal">false</span></span><br><span class="line">            <span class="comment">//轮训所要的解码器</span></span><br><span class="line">            <span class="keyword">var</span> j = <span class="number">0</span></span><br><span class="line">            <span class="keyword">while</span> (j &lt; types.size &amp;&amp; !found) &#123;</span><br><span class="line">                <span class="keyword">if</span> (types[j] == <span class="string">&quot;video/avc&quot;</span>) &#123;</span><br><span class="line">                    found = <span class="literal">true</span></span><br><span class="line">                &#125;</span><br><span class="line">                j++</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (!found) &#123;</span><br><span class="line">                i++</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            &#125;</span><br><span class="line">            codecInfo = info</span><br><span class="line">            i++</span><br><span class="line">        &#125;</span><br><span class="line">        Log.i(<span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>, <span class="string">&quot;found &quot;</span> + codecInfo?.name + <span class="string">&quot; supporting video/avc&quot;</span>)</span><br><span class="line">        <span class="keyword">val</span> capabilities = codecInfo?.getCapabilitiesForType(<span class="string">&quot;video/avc&quot;</span>)</span><br><span class="line">        <span class="keyword">return</span> capabilities?.colorFormats</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">createMediaFormat</span><span class="params">(activity: <span class="type">Activity</span>)</span></span>: MediaFormat? &#123;</span><br><span class="line">        <span class="keyword">val</span> decorView = activity.window?.decorView ?: <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">        <span class="keyword">val</span> rect = Rect()</span><br><span class="line">        decorView.getWindowVisibleDisplayFrame(rect)</span><br><span class="line">        <span class="keyword">val</span> display = activity.windowManager.defaultDisplay</span><br><span class="line">        <span class="keyword">val</span> width = display.width</span><br><span class="line">        <span class="keyword">val</span> height = display.height</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> mediaFormat =</span><br><span class="line">            MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);</span><br><span class="line">        mediaFormat.setInteger(</span><br><span class="line">            MediaFormat.KEY_COLOR_FORMAT,</span><br><span class="line">            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible</span><br><span class="line">        )</span><br><span class="line">        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height)</span><br><span class="line">        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, <span class="number">10</span>)</span><br><span class="line">        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, <span class="number">5</span>)</span><br><span class="line">        <span class="keyword">return</span> mediaFormat</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 保存视频文件</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">store</span><span class="params">(context: <span class="type">Context</span>, dataList: <span class="type">ArrayList</span>&lt;<span class="type">String</span>?&gt;)</span></span> &#123;</span><br><span class="line">        <span class="keyword">val</span> activity = ActivityLifecycle.getCurrentActivity()</span><br><span class="line">        Log.i(<span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>, <span class="string">&quot;store ThreadName:<span class="subst">$&#123;Thread.currentThread().name&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> (activity != <span class="literal">null</span>) &#123;</span><br><span class="line">            getMediaCodecList()</span><br><span class="line">            <span class="keyword">val</span> mediaFormat = createMediaFormat(activity)</span><br><span class="line">            <span class="keyword">if</span> (mediaFormat != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">val</span> file = createVideoFile(activity)</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    writeVideo(mediaFormat, dataList, file)</span><br><span class="line">                &#125; <span class="keyword">catch</span> (e: Throwable) &#123;</span><br><span class="line">                    file.delete()</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        dataList.forEach &#123; path -&gt;</span><br><span class="line">            <span class="keyword">if</span> (path != <span class="literal">null</span>) File(path).delete()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createVideoFile</span><span class="params">(activity: <span class="type">Activity</span>)</span></span>: File &#123;</span><br><span class="line">        <span class="keyword">var</span> file = activity.getExternalFilesDir(<span class="string">&quot;error_recode&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> (file == <span class="literal">null</span>) file = activity.cacheDir!!</span><br><span class="line">        file = File(file, <span class="string">&quot;error_aac&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!file.exists() || file.isFile) file.mkdirs()</span><br><span class="line">        <span class="keyword">return</span> File(file, <span class="string">&quot;<span class="subst">$&#123;System.currentTimeMillis()&#125;</span>.mp4&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">writeVideo</span><span class="params">(mediaFormat: <span class="type">MediaFormat</span>, dataList: <span class="type">ArrayList</span>&lt;<span class="type">String</span>?&gt;, file: <span class="type">File</span>)</span></span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">val</span> mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);</span><br><span class="line">            <span class="keyword">val</span> mediaMuxer =</span><br><span class="line">                MediaMuxer(file.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)</span><br><span class="line"></span><br><span class="line">            mediaCodec.configure(mediaFormat, <span class="literal">null</span>, <span class="literal">null</span>, MediaCodec.CONFIGURE_FLAG_ENCODE)</span><br><span class="line">            mediaCodec.start()</span><br><span class="line"></span><br><span class="line">            writeVideoFrame(dataList, mediaCodec, mediaMuxer, mediaFormat)</span><br><span class="line"></span><br><span class="line">            mediaCodec.stop()</span><br><span class="line">            mediaCodec.release()</span><br><span class="line"></span><br><span class="line">            mediaMuxer.stop()</span><br><span class="line">            mediaMuxer.release()</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line">            e.printStackTrace()</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">writeVideoFrame</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">        dataList: <span class="type">ArrayList</span>&lt;<span class="type">String</span>?&gt;,</span></span></span><br><span class="line"><span class="params"><span class="function">        mediaCodec: <span class="type">MediaCodec</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">        mediaMuxer: <span class="type">MediaMuxer</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">        mediaFormat: <span class="type">MediaFormat</span></span></span></span><br><span class="line"><span class="params"><span class="function">    )</span></span> &#123;</span><br><span class="line">        <span class="keyword">val</span> mediaCodecBufferInfo = MediaCodec.BufferInfo()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> generateIndex = <span class="number">0</span></span><br><span class="line">        dataList.forEach &#123; path -&gt;</span><br><span class="line">            <span class="keyword">if</span> (path != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">val</span> inputBufferIndex: <span class="built_in">Int</span> = mediaCodec.dequeueInputBuffer(<span class="number">10000</span>)</span><br><span class="line">                <span class="keyword">if</span> (inputBufferIndex &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">val</span> bitmap = readBitmap(path) ?: <span class="keyword">return</span><span class="symbol">@forEach</span></span><br><span class="line">                    <span class="keyword">val</span> ptsUsec =</span><br><span class="line">                        <span class="number">132L</span> + generateIndex * <span class="number">1000000</span> / mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE)</span><br><span class="line">                    <span class="keyword">val</span> yuvArray = covertBitmap(bitmap)</span><br><span class="line">                    <span class="keyword">val</span> inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex)</span><br><span class="line">                    inputBuffer?.clear()</span><br><span class="line">                    inputBuffer?.put(yuvArray)</span><br><span class="line">                    mediaCodec.queueInputBuffer(inputBufferIndex, <span class="number">0</span>, yuvArray.size, ptsUsec, <span class="number">0</span>)</span><br><span class="line">                    bitmap.recycle()</span><br><span class="line">                    drainEncoder(<span class="literal">false</span>, mediaCodecBufferInfo, mediaCodec, mediaMuxer)</span><br><span class="line">                    generateIndex++</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> inputBufferIndex = mediaCodec.dequeueInputBuffer(<span class="number">10000</span>)</span><br><span class="line">        <span class="keyword">if</span> (inputBufferIndex &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">val</span> ptsUsec =</span><br><span class="line">                <span class="number">132L</span> + generateIndex * <span class="number">1000000</span> / mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE)</span><br><span class="line">            mediaCodec.queueInputBuffer(</span><br><span class="line">                inputBufferIndex, <span class="number">0</span>, <span class="number">0</span>, ptsUsec,</span><br><span class="line">                MediaCodec.BUFFER_FLAG_END_OF_STREAM</span><br><span class="line">            )</span><br><span class="line">            drainEncoder(<span class="literal">true</span>, mediaCodecBufferInfo, mediaCodec, mediaMuxer)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> mMuxerStarted = <span class="literal">false</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> mTrackIndex = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 编辑编码器</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">drainEncoder</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">        isEnd: <span class="type">Boolean</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">        mediaCodecBufferInfo: <span class="type">MediaCodec</span>.<span class="type">BufferInfo</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">        mediaCodec: <span class="type">MediaCodec</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">        mediaMuxer: <span class="type">MediaMuxer</span></span></span></span><br><span class="line"><span class="params"><span class="function">    )</span></span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isEnd) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                mediaCodec.signalEndOfInputStream()</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="keyword">val</span> encoderStatus = mediaCodec.dequeueOutputBuffer(mediaCodecBufferInfo, <span class="number">10000L</span>)</span><br><span class="line">            <span class="keyword">if</span> (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) &#123;</span><br><span class="line">                <span class="keyword">if</span> (!isEnd) &#123;</span><br><span class="line">                    <span class="keyword">break</span> <span class="comment">// out of while</span></span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    Log.i(</span><br><span class="line">                        <span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;no output available, spinning to await EOS&quot;</span></span><br><span class="line">                    )</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) &#123;</span><br><span class="line">                <span class="keyword">if</span> (mMuxerStarted) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> RuntimeException(<span class="string">&quot;format changed twice&quot;</span>)</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">val</span> mediaFormat = mediaCodec.outputFormat</span><br><span class="line">                mTrackIndex = mediaMuxer.addTrack(mediaFormat)</span><br><span class="line">                mediaMuxer.start()</span><br><span class="line">                mMuxerStarted = <span class="literal">true</span></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (encoderStatus &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                Log.i(</span><br><span class="line">                    <span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>,</span><br><span class="line">                    <span class="string">&quot;unexpected result from encoder.dequeueOutputBuffer: <span class="variable">$encoderStatus</span>&quot;</span></span><br><span class="line">                )</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">val</span> outputBuffer = mediaCodec.getOutputBuffer(encoderStatus)</span><br><span class="line">                    ?: <span class="keyword">throw</span> java.lang.RuntimeException(</span><br><span class="line">                        <span class="string">&quot;encoderOutputBuffer &quot;</span></span><br><span class="line">                                + encoderStatus + <span class="string">&quot; was null&quot;</span></span><br><span class="line">                    )</span><br><span class="line">                <span class="keyword">if</span> ((mediaCodecBufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != <span class="number">0</span>) &#123;</span><br><span class="line">                    Log.d(</span><br><span class="line">                        <span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;ignoring BUFFER_FLAG_CODEC_CONFIG&quot;</span></span><br><span class="line">                    )</span><br><span class="line">                    mediaCodecBufferInfo.size = <span class="number">0</span></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (mediaCodecBufferInfo.size != <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (!mMuxerStarted) &#123;</span><br><span class="line">                        <span class="keyword">throw</span> java.lang.RuntimeException(<span class="string">&quot;muxer hasn&#x27;t started&quot;</span>)</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// adjust the ByteBuffer values to match BufferInfo</span></span><br><span class="line">                    outputBuffer.position(mediaCodecBufferInfo.offset)</span><br><span class="line">                    outputBuffer.limit(mediaCodecBufferInfo.offset + mediaCodecBufferInfo.size)</span><br><span class="line">                    Log.d(</span><br><span class="line">                        <span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>,</span><br><span class="line">                        (<span class="string">&quot;BufferInfo: &quot;</span> + mediaCodecBufferInfo.offset + <span class="string">&quot;,&quot;</span></span><br><span class="line">                                + mediaCodecBufferInfo.size + <span class="string">&quot;,&quot;</span></span><br><span class="line">                                + mediaCodecBufferInfo.presentationTimeUs)</span><br><span class="line">                    )</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, mediaCodecBufferInfo)</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (e: java.lang.Exception) &#123;</span><br><span class="line">                        Log.i(<span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>, <span class="string">&quot;Too many frames&quot;</span>)</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">                mediaCodec.releaseOutputBuffer(encoderStatus, <span class="literal">false</span>)</span><br><span class="line">                <span class="keyword">if</span> ((mediaCodecBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (!isEnd) &#123;</span><br><span class="line">                        Log.i(</span><br><span class="line">                            <span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>,</span><br><span class="line">                            <span class="string">&quot;reached end of stream unexpectedly&quot;</span></span><br><span class="line">                        )</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        Log.i(<span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>, <span class="string">&quot;end of stream reached&quot;</span>)</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">break</span> <span class="comment">// out of while</span></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</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">/**</span></span><br><span class="line"><span class="comment">     * 图片转成 yuv数组</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">covertBitmap</span><span class="params">(bitmap: <span class="type">Bitmap</span>)</span></span>: ByteArray &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> inputWidth = bitmap.width / <span class="number">4</span> * <span class="number">4</span></span><br><span class="line">        <span class="keyword">val</span> inputHeight = bitmap.height / <span class="number">4</span> * <span class="number">4</span></span><br><span class="line">        <span class="keyword">val</span> argb = IntArray(inputWidth * inputHeight)</span><br><span class="line"></span><br><span class="line">        <span class="comment">//Log.i(TAG, &quot;scaled : &quot; + scaled);</span></span><br><span class="line">        bitmap.getPixels(argb, <span class="number">0</span>, inputWidth, <span class="number">0</span>, <span class="number">0</span>, inputWidth, inputHeight)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">val</span> yuv = ByteArray(inputWidth * inputHeight * <span class="number">3</span> / <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">        encodeYUV420SP(yuv, argb, inputWidth, inputHeight)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> yuv</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * rgba 转 yuv420 sp</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">encodeYUV420SP</span><span class="params">(yuv420sp: <span class="type">ByteArray</span>, argb: <span class="type">IntArray</span>, width: <span class="type">Int</span>, height: <span class="type">Int</span>)</span></span> &#123;</span><br><span class="line">        <span class="keyword">val</span> frameSize = width * height</span><br><span class="line">        <span class="keyword">var</span> yIndex = <span class="number">0</span></span><br><span class="line">        <span class="keyword">var</span> uvIndex = frameSize</span><br><span class="line">        <span class="keyword">var</span> a: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> R: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> G: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> B: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> Y: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> U: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> V: <span class="built_in">Int</span></span><br><span class="line">        <span class="keyword">var</span> index = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> (j <span class="keyword">in</span> <span class="number">0</span> until height) &#123;</span><br><span class="line">            <span class="keyword">for</span> (i <span class="keyword">in</span> <span class="number">0</span> until width) &#123;</span><br><span class="line">                a = argb[index] and -<span class="number">0x1000000</span> shr <span class="number">24</span> <span class="comment">// a is not used obviously</span></span><br><span class="line">                R = argb[index] and <span class="number">0xff0000</span> shr <span class="number">16</span></span><br><span class="line">                G = argb[index] and <span class="number">0xff00</span> shr <span class="number">8</span></span><br><span class="line">                B = argb[index] and <span class="number">0xff</span> shr <span class="number">0</span></span><br><span class="line">                <span class="comment">// well known RGB to YUV algorithm</span></span><br><span class="line">                Y = (<span class="number">66</span> * R + <span class="number">129</span> * G + <span class="number">25</span> * B + <span class="number">128</span> shr <span class="number">8</span>) + <span class="number">16</span></span><br><span class="line">                V = (-<span class="number">38</span> * R - <span class="number">74</span> * G + <span class="number">112</span> * B + <span class="number">128</span> shr <span class="number">8</span>) + <span class="number">128</span> <span class="comment">// Previously U</span></span><br><span class="line">                U = (<span class="number">112</span> * R - <span class="number">94</span> * G - <span class="number">18</span> * B + <span class="number">128</span> shr <span class="number">8</span>) + <span class="number">128</span> <span class="comment">// Previously V</span></span><br><span class="line">                yuv420sp[yIndex++] = (<span class="keyword">if</span> (Y &lt; <span class="number">0</span>) <span class="number">0</span> <span class="keyword">else</span> <span class="keyword">if</span> (Y &gt; <span class="number">255</span>) <span class="number">255</span> <span class="keyword">else</span> Y).toByte()</span><br><span class="line">                <span class="keyword">if</span> (j % <span class="number">2</span> == <span class="number">0</span> &amp;&amp; index % <span class="number">2</span> == <span class="number">0</span>) &#123;</span><br><span class="line">                    yuv420sp[uvIndex++] = (<span class="keyword">if</span> (V &lt; <span class="number">0</span>) <span class="number">0</span> <span class="keyword">else</span> <span class="keyword">if</span> (V &gt; <span class="number">255</span>) <span class="number">255</span> <span class="keyword">else</span> V).toByte()</span><br><span class="line">                    yuv420sp[uvIndex++] = (<span class="keyword">if</span> (U &lt; <span class="number">0</span>) <span class="number">0</span> <span class="keyword">else</span> <span class="keyword">if</span> (U &gt; <span class="number">255</span>) <span class="number">255</span> <span class="keyword">else</span> U).toByte()</span><br><span class="line">                &#125;</span><br><span class="line">                index++</span><br><span class="line">            &#125;</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">/**</span></span><br><span class="line"><span class="comment">     * 从本地文件中读取并且解码图片生成bitmap</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">readBitmap</span><span class="params">(path: <span class="type">String</span>)</span></span>: Bitmap? &#123;</span><br><span class="line">        <span class="keyword">var</span> fis: FileInputStream? = <span class="literal">null</span></span><br><span class="line">        <span class="keyword">var</span> baos: ByteArrayOutputStream? = <span class="literal">null</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            fis = FileInputStream(path)</span><br><span class="line">            baos = ByteArrayOutputStream()</span><br><span class="line"></span><br><span class="line">            <span class="keyword">val</span> fileBuffer = ByteArray(<span class="number">2048</span>)</span><br><span class="line">            <span class="keyword">var</span> len: <span class="built_in">Int</span></span><br><span class="line">            <span class="keyword">while</span> (fis.read(fileBuffer).also &#123; len = it &#125; &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">for</span> (i <span class="keyword">in</span> <span class="number">0</span> until len) &#123;</span><br><span class="line">                    fileBuffer[i] = fileBuffer[i].xor(<span class="number">0xF</span>)</span><br><span class="line">                &#125;</span><br><span class="line">                baos.write(fileBuffer, <span class="number">0</span>, len)</span><br><span class="line">            &#125;</span><br><span class="line">            baos.flush()</span><br><span class="line">            <span class="keyword">val</span> imageBuffer = baos.toByteArray()</span><br><span class="line">            <span class="keyword">val</span> bitmap = BitmapFactory.decodeByteArray(imageBuffer, <span class="number">0</span>, imageBuffer.size)</span><br><span class="line">            Log.i(<span class="string">&quot;ErrorRecodeVideoUtils&quot;</span>, <span class="string">&quot;bitmap:<span class="variable">$bitmap</span>&quot;</span>)</span><br><span class="line">            <span class="keyword">return</span> bitmap</span><br><span class="line">        &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                fis?.close()</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                baos?.close()</span><br><span class="line">            &#125; <span class="keyword">catch</span> (e: Exception) &#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2021/06/04/android_recode_screen/</id>
    <link href="https://gloomyer.com/2021/06/04/android_recode_screen/"/>
    <published>2021-06-04T06:30:00.000Z</published>
    <summary>虽然app实现了捕捉崩溃日志的功能,但是在bug复现过程中 还是产生了不少的苦难，在接触h5之后, 知晓他们可以通过记录dom事件 来实现录像，突然感觉app实现录像也没有那么困难</summary>
    <title>Android App 行为录像</title>
    <updated>2021-06-04T06:30:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="Camera" scheme="https://gloomyer.com/tags/Camera/"/>
    <content>
      <![CDATA[<blockquote><p> android camera2 拍照方向问题</p><span id="more"></span></blockquote><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>&#x2F;&#x2F;设置拍照方向<br>mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));</p><p>api不生效，查询后发现这句话依赖设备底层实现。</p><p>所以得去直接读取图片方向然后旋转来解决这个问题</p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>工具类代码:</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">getJpegOrientation</span><span class="params">(<span class="type">byte</span>[] jpeg)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (jpeg == <span class="literal">null</span>) &#123;</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"></span><br><span class="line">    <span class="type">int</span> <span class="variable">offset</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (offset + <span class="number">3</span> &lt; jpeg.length &amp;&amp; (jpeg[offset++] &amp; <span class="number">0xFF</span>) == <span class="number">0xFF</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">marker</span> <span class="operator">=</span> jpeg[offset] &amp; <span class="number">0xFF</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (marker == <span class="number">0xFF</span>) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        offset++;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (marker == <span class="number">0xD8</span> || marker == <span class="number">0x01</span>) &#123;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (marker == <span class="number">0xD9</span> || marker == <span class="number">0xDA</span>) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        length = pack(jpeg, offset, <span class="number">2</span>, <span class="literal">false</span>);</span><br><span class="line">        <span class="keyword">if</span> (length &lt; <span class="number">2</span> || offset + length &gt; jpeg.length) &#123;</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"></span><br><span class="line">        <span class="keyword">if</span> (marker == <span class="number">0xE1</span> &amp;&amp; length &gt;= <span class="number">8</span> &amp;&amp;</span><br><span class="line">                pack(jpeg, offset + <span class="number">2</span>, <span class="number">4</span>, <span class="literal">false</span>) == <span class="number">0x45786966</span> &amp;&amp;</span><br><span class="line">                pack(jpeg, offset + <span class="number">6</span>, <span class="number">2</span>, <span class="literal">false</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            offset += <span class="number">8</span>;</span><br><span class="line">            length -= <span class="number">8</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        offset += length;</span><br><span class="line">        length = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (length &gt; <span class="number">8</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">tag</span> <span class="operator">=</span> pack(jpeg, offset, <span class="number">4</span>, <span class="literal">false</span>);</span><br><span class="line">        <span class="keyword">if</span> (tag != <span class="number">0x49492A00</span> &amp;&amp; tag != <span class="number">0x4D4D002A</span>) &#123;</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">        <span class="type">boolean</span> <span class="variable">littleEndian</span> <span class="operator">=</span> (tag == <span class="number">0x49492A00</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> pack(jpeg, offset + <span class="number">4</span>, <span class="number">4</span>, littleEndian) + <span class="number">2</span>;</span><br><span class="line">        <span class="keyword">if</span> (count &lt; <span class="number">10</span> || count &gt; length) &#123;</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">        offset += count;</span><br><span class="line">        length -= count;</span><br><span class="line"></span><br><span class="line">        count = pack(jpeg, offset - <span class="number">2</span>, <span class="number">2</span>, littleEndian);</span><br><span class="line">        <span class="keyword">while</span> (count-- &gt; <span class="number">0</span> &amp;&amp; length &gt;= <span class="number">12</span>) &#123;</span><br><span class="line">            tag = pack(jpeg, offset, <span class="number">2</span>, littleEndian);</span><br><span class="line">            <span class="keyword">if</span> (tag == <span class="number">0x0112</span>) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">orientation</span> <span class="operator">=</span> pack(jpeg, offset + <span class="number">8</span>, <span class="number">2</span>, littleEndian);</span><br><span class="line">                <span class="keyword">switch</span> (orientation) &#123;</span><br><span class="line">                    <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">                        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">                    <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">                        <span class="keyword">return</span> <span class="number">180</span>;</span><br><span class="line">                    <span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line">                        <span class="keyword">return</span> <span class="number">90</span>;</span><br><span class="line">                    <span class="keyword">case</span> <span class="number">8</span>:</span><br><span class="line">                        <span class="keyword">return</span> <span class="number">270</span>;</span><br><span class="line">                &#125;</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">            offset += <span class="number">12</span>;</span><br><span class="line">            length -= <span class="number">12</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</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"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">pack</span><span class="params">(<span class="type">byte</span>[] bytes, <span class="type">int</span> offset, <span class="type">int</span> length,</span></span><br><span class="line"><span class="params">                            <span class="type">boolean</span> littleEndian)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">step</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">if</span> (littleEndian) &#123;</span><br><span class="line">        offset += length - <span class="number">1</span>;</span><br><span class="line">        step = -<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">while</span> (length-- &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        value = (value &lt;&lt; <span class="number">8</span>) | (bytes[offset] &amp; <span class="number">0xFF</span>);</span><br><span class="line">        offset += step;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用代码:</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><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></pre></td><td class="code"><pre><span class="line"> mImageReader.setOnImageAvailableListener(<span class="keyword">new</span> <span class="title class_">ImageReader</span>.OnImageAvailableListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">onImageAvailable</span><span class="params">(ImageReader reader)</span> &#123;</span><br><span class="line">        Log.i(TAG, <span class="string">&quot;Image Available!&quot;</span>);</span><br><span class="line">        <span class="type">Image</span> <span class="variable">picture</span> <span class="operator">=</span> reader.acquireLatestImage();</span><br><span class="line">        </span><br><span class="line">        Image.<span class="type">Plane</span>[] <span class="variable">planes</span> <span class="operator">=</span> picture.getPlanes();</span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">buffer</span> <span class="operator">=</span> planes[<span class="number">0</span>].getBuffer();</span><br><span class="line">        <span class="type">ByteArrayInputStream</span> <span class="variable">bis</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="type">byte</span>[] <span class="variable">data</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">byte</span>[buffer.remaining()];</span><br><span class="line">        buffer.get(data);</span><br><span class="line">        bis = <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(data);</span><br><span class="line">        <span class="type">int</span> <span class="variable">naturalOrientation</span> <span class="operator">=</span> BitmapUtils.getNaturalOrientation(data);</span><br><span class="line"></span><br><span class="line">        Bitmap bitmap;</span><br><span class="line">        <span class="keyword">if</span> (naturalOrientation != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">//旋转图片</span></span><br><span class="line">            <span class="type">Bitmap</span> <span class="variable">thumb</span> <span class="operator">=</span> BitmapFactory.decodeByteArray(data, <span class="number">0</span>, data.length);</span><br><span class="line">            <span class="type">Matrix</span> <span class="variable">matrix</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Matrix</span>();</span><br><span class="line">            matrix.postRotate(naturalOrientation);</span><br><span class="line">            bitmap = Bitmap.createBitmap(thumb, <span class="number">0</span>, <span class="number">0</span>, thumb.getWidth(), thumb.getHeight(), matrix, <span class="literal">true</span>);</span><br><span class="line">                thumb.recycle();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            bitmap = BitmapFactory.decodeStream(bis);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//use bitmap</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2021/04/06/android_camera2_orientation/</id>
    <link href="https://gloomyer.com/2021/04/06/android_camera2_orientation/"/>
    <published>2021-04-06T07:56:00.000Z</published>
    <summary>//设置拍照方向，mCaptureBuilder.set(CaptureRequest.JPEGORIENTATION, ORIENTATION.get(rotation));</summary>
    <title>Android Camera2 拍照方向问题</title>
    <updated>2021-04-06T07:56:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="前端与脚本" scheme="https://gloomyer.com/categories/%E5%89%8D%E7%AB%AF%E4%B8%8E%E8%84%9A%E6%9C%AC/"/>
    <category term="JavaScript" scheme="https://gloomyer.com/tags/JavaScript/"/>
    <category term="Vue" scheme="https://gloomyer.com/tags/Vue/"/>
    <content>
      <![CDATA[<blockquote><p>如何解决 原生js修改 input标签 vue绑定数据不更新</p><span id="more"></span></blockquote><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>需求是写浏览器插件来实现自动填表</p><p>发现一个问题 input直接修改value 后 点击提交系统不识别 </p><p>必须人为在输入框输入任意内容后才识别</p><p>先通过堆栈报错 发现网站是vue的</p><p>然后做了一个简单测试，发现确实如此。</p><h2 id="有问题的demo"><a href="#有问题的demo" class="headerlink" title="有问题的demo"></a>有问题的demo</h2><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></pre></td><td class="code"><pre><span class="line">&lt;html&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.jsdelivr.net/npm/vue@2.6.12&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">function</span> <span class="title function_">change</span>(<span class="params">v</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;change....&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">id</span>=<span class="string">&quot;in&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">v-model</span>=<span class="string">&quot;inputValue&quot;</span> <span class="attr">onchange</span>=<span class="string">&quot;change()&quot;</span> @<span class="attr">change</span>=<span class="string">&quot;change2&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">&quot;click&quot;</span>&gt;</span>click<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            var element = document.getElementById(&#x27;in&#x27;);</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            element.value = &#x27;123&#x27;;</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">        &quot;</span>&gt;</span>原生js触发<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">data</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">inputValue</span>: <span class="string">&#x27;&#x27;</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">methods</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">click</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                    <span class="title function_">alert</span>(<span class="variable language_">this</span>.<span class="property">inputValue</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">change2</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;vue change....&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><h2 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h2><p>发现直接修改value 不光vue不识别 且onchange方法也不会触发</p><p>吃了不熟悉vue的亏，其实vue输入框和数据绑定是通过浏览器input的事件同步的！</p><p>最开始还以为是利用onchange  去手动触发onchange 也是不行</p><p>所以只需要在修改内容后手动触发input事件即可</p><h2 id="解决后的代码"><a href="#解决后的代码" class="headerlink" title="解决后的代码"></a>解决后的代码</h2><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></pre></td><td class="code"><pre><span class="line">&lt;html&gt;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;https://cdn.jsdelivr.net/npm/vue@2.6.12&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">function</span> <span class="title function_">change</span>(<span class="params">v</span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;change....&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">id</span>=<span class="string">&quot;in&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">v-model</span>=<span class="string">&quot;inputValue&quot;</span> <span class="attr">onchange</span>=<span class="string">&quot;change()&quot;</span> @<span class="attr">change</span>=<span class="string">&quot;change2&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">&quot;click&quot;</span>&gt;</span>click<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onclick</span>=<span class="string">&quot;</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            var element = document.getElementById(&#x27;in&#x27;);</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            element.value = &#x27;123&#x27;;</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            var evt = document.createEvent(&#x27;HTMLEvents&#x27;);</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            evt.initEvent(&#x27;input&#x27;, false, true);</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">            element.dispatchEvent(evt);</span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">        &quot;</span>&gt;</span>原生js触发<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">el</span>: <span class="string">&#x27;#app&#x27;</span>,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">data</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">inputValue</span>: <span class="string">&#x27;&#x27;</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            <span class="attr">methods</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">click</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                    <span class="title function_">alert</span>(<span class="variable language_">this</span>.<span class="property">inputValue</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                <span class="attr">change2</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;vue change....&quot;</span>);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">                &#125;,</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">            &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">        &#125;);</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2021/03/08/js_change_vue/</id>
    <link href="https://gloomyer.com/2021/03/08/js_change_vue/"/>
    <published>2021-03-08T03:36:31.000Z</published>
    <summary>需求是写浏览器插件来实现自动填表，发现一个问题 input直接修改value 后 点击提交系统不识别，必须人为在输入框输入任意内容后才识别，先通过堆栈报错 发现网站是vue的</summary>
    <title>原生 JS 修改 Vue 绑定元素的值</title>
    <updated>2021-03-08T03:36:31.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="WebView" scheme="https://gloomyer.com/tags/WebView/"/>
    <category term="嵌套滚动" scheme="https://gloomyer.com/tags/%E5%B5%8C%E5%A5%97%E6%BB%9A%E5%8A%A8/"/>
    <content>
      <![CDATA[<blockquote><p>Android 在可滚动View中嵌套WebView</p></blockquote><span id="more"></span><h2 id="为啥这么做"><a href="#为啥这么做" class="headerlink" title="为啥这么做?"></a>为啥这么做?</h2><p>项目中商品详情顶部UI是利用原生渲染，底部详情为了运营方便编辑走的是H5页面</p><p>这种场景下 就需要在一个可以滚动的View中去嵌套WebView</p><p>最开始项目外部用的NestedScrollView 顶部利用了一个关闭了嵌套滚动的RecyclerView 实现 底部是webView</p><p>类似这样的结构</p><figure class="highlight xml"><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 class="tag">&lt;<span class="name">NestedScrollView</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">LinearLayout</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">RecyclerView</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">RecyclerView</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">WebView</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">WebView</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">LinearLayout</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">NestedScrollView</span>&gt;</span></span><br></pre></td></tr></table></figure><p>webview 底部商品详情是相对固定的</p><p>但是后续业务导致webview 底部又开始有推荐商品，是一个分页接口的列表。</p><p>造成了一个神奇的现象，webview高度无限增长</p><p>在chrome webview调试工具中看到 webview开始加载后高度不停的增高</p><p>不一会就变成了几万像素</p><h2 id="解决方案1-适用于WebView加载的内容是固定的"><a href="#解决方案1-适用于WebView加载的内容是固定的" class="headerlink" title="解决方案1,适用于WebView加载的内容是固定的"></a>解决方案1,适用于WebView加载的内容是固定的</h2><p>直接替换NestedScrollView为ScrollView</p><p>如果webview 加载的内容需要滚动事件，此方法不可用.</p><h2 id="解决方案2-适用于WebView加载的内容是固定的"><a href="#解决方案2-适用于WebView加载的内容是固定的" class="headerlink" title="解决方案2,适用于WebView加载的内容是固定的"></a>解决方案2,适用于WebView加载的内容是固定的</h2><p>这种方案其实非常简单，在WebView 加载完成之后获取内容高度 给webview 钉死高度</p><p>如果webview 加载的内容需要滚动事件，此方法不可用.</p><p>具体没实现 就不贴代码了</p><p>思路是webview有个方法是getContentHeight()可以获取网页内容高度</p><h2 id="解决方案3-4"><a href="#解决方案3-4" class="headerlink" title="解决方案3&#x2F;4"></a>解决方案3&#x2F;4</h2><p>3&#x2F;4的前提都一样，首先webview 不支持嵌套滚动，第一步是给webview 添加嵌套滚动</p><p>这是大佬写好的嵌套滚动WebView<a href="https://stackoverflow.com/questions/57654466/nestedwebview-working-properly-with-scrollingviewbehavior">NestedWebView</a></p><p>3&#x2F;4的区别就是是否继续NestedScrollView</p><p>继续用要重写NestScrollView 去重写嵌套滚动的几个方法，来派发事件 实现类似吸顶效果</p><p>但是有个取巧的方案，那就是CoordinatorLayout</p><p>我用了取巧的方案，因为我觉得我自己重写事件派发也未必有CoordinatorLayout写的好，不如直接用好了。</p><p>最后的项目结构就是:</p><p>记得关闭RecyclerView的嵌套滚动哟.</p><figure class="highlight xml"><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">androidx.coordinatorlayout.widget.CoordinatorLayout</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:id</span>=<span class="string">&quot;@+id/coordinator_layout&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:layout_height</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">android:background</span>=<span class="string">&quot;@null&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!--     顶部内容区域       --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">com.google.android.material.appbar.AppBarLayout</span></span></span><br><span class="line"><span class="tag">            <span class="attr">android:id</span>=<span class="string">&quot;@+id/appbar_layout&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">android:layout_height</span>=<span class="string">&quot;wrap_content&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">android:background</span>=<span class="string">&quot;@null&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">android:orientation</span>=<span class="string">&quot;vertical&quot;</span></span></span><br><span class="line"><span class="tag">            <span class="attr">app:elevation</span>=<span class="string">&quot;0dp&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">            <span class="tag">&lt;<span class="name">FrameLayout</span></span></span><br><span class="line"><span class="tag">                <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">android:layout_height</span>=<span class="string">&quot;wrap_content&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">app:layout_scrollFlags</span>=<span class="string">&quot;scroll&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">&lt;!--     顶部数据区域    --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">androidx.recyclerview.widget.RecyclerView</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:id</span>=<span class="string">&quot;@+id/rv_header&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:layout_height</span>=<span class="string">&quot;wrap_content&quot;</span> /&gt;</span></span><br><span class="line"></span><br><span class="line">                <span class="comment">&lt;!--    跑马灯数据    --&gt;</span></span><br><span class="line">                <span class="tag">&lt;<span class="name">ViewFlipper</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:id</span>=<span class="string">&quot;@+id/vf_flipper&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:layout_width</span>=<span class="string">&quot;200dp&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:layout_height</span>=<span class="string">&quot;wrap_content&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:layout_marginStart</span>=<span class="string">&quot;12dp&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:layout_marginTop</span>=<span class="string">&quot;80dp&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:inAnimation</span>=<span class="string">&quot;@anim/anim_in&quot;</span></span></span><br><span class="line"><span class="tag">                    <span class="attr">android:outAnimation</span>=<span class="string">&quot;@anim/anim_out&quot;</span> /&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">FrameLayout</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="tag">&lt;/<span class="name">com.google.android.material.appbar.AppBarLayout</span>&gt;</span></span><br><span class="line"></span><br><span class="line">         <span class="comment">&lt;!--     商品详情数据和推荐商品 h5    --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">com.fingogroup.fingo.lib.widget.web.NestedWebView</span></span></span><br><span class="line"><span class="tag">                <span class="attr">android:id</span>=<span class="string">&quot;@+id/wb_web&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">android:layout_width</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">android:layout_height</span>=<span class="string">&quot;match_parent&quot;</span></span></span><br><span class="line"><span class="tag">                <span class="attr">app:layout_behavior</span>=<span class="string">&quot;@string/appbar_scrolling_view_behavior&quot;</span> /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">androidx.coordinatorlayout.widget.CoordinatorLayout</span>&gt;</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2020/08/25/android_nested_web_view/</id>
    <link href="https://gloomyer.com/2020/08/25/android_nested_web_view/"/>
    <published>2020-08-25T14:00:00.000Z</published>
    <summary>项目中商品详情顶部UI是利用原生渲染，底部详情为了运营方便编辑走的是H5页面，这种场景下 就需要在一个可以滚动的View中去嵌套WebView，最开始项目外部用的NestedScrollView 顶部利用了一个关闭了嵌套滚动的Recycle</summary>
    <title>Android 滚动 View 嵌套 WebView</title>
    <updated>2020-08-25T14:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="安全与逆向" scheme="https://gloomyer.com/categories/%E5%AE%89%E5%85%A8%E4%B8%8E%E9%80%86%E5%90%91/"/>
    <category term="Xposed" scheme="https://gloomyer.com/tags/Xposed/"/>
    <category term="Hook" scheme="https://gloomyer.com/tags/Hook/"/>
    <content>
      <![CDATA[<blockquote><p>Android Xposed 开发</p></blockquote><span id="more"></span><h2 id="啥是Xposed"><a href="#啥是Xposed" class="headerlink" title="啥是Xposed"></a>啥是Xposed</h2><p>Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务，可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务，基于它可以制作出许多功能强大的模块，且在功能不冲突的情况下同时运作。</p><p>xposed的主要作用是hook应用方法，动态劫持方法的运行。</p><p><font color='red'>重点说明，Xposed插件是运行在劫持的APP内部！</font></p><h2 id="开发入门"><a href="#开发入门" class="headerlink" title="开发入门"></a>开发入门</h2><h3 id="项目配置"><a href="#项目配置" class="headerlink" title="项目配置"></a>项目配置</h3><p>修改AndroidManifest文件，在application节点下加入</p><figure class="highlight xml"><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta-data</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;xposedmodule&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:value</span>=<span class="string">&quot;true&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta-data</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;xposeddescription&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:value</span>=<span class="string">&quot;xposed插件描述&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta-data</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;xposedminversion&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:value</span>=<span class="string">&quot;30&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><p>注意这里面的三个meta-data标签的name不能错误，不然xposed框架apk无法识别自定义编写的xposed模块。</p><p>第一个是描述这是一个xposed插件，第二个在xposed manager 中对你插件的描述，第三个是表述最低支持的xposed版本。</p><p>修改app下的build.gradle 文件,dependencies 下增加</p><figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">compileOnly <span class="string">&#x27;de.robv.android.xposed:api:82&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="开发一个劫持Activity获取启动参数的小插件"><a href="#开发一个劫持Activity获取启动参数的小插件" class="headerlink" title="开发一个劫持Activity获取启动参数的小插件"></a>开发一个劫持Activity获取启动参数的小插件</h3><p>创建一个类，实现IXposedHookLoadPackage 接口 类似：</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><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Classname</span> IXposedHookLoadPackage</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> hook</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2019-12-13 09:49</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by gloomy</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Hook</span> <span class="keyword">implements</span> <span class="title class_">IXposedHookLoadPackage</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">handleLoadPackage</span><span class="params">(XC_LoadPackage.LoadPackageParam lpParam)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        XposedHelpers.findAndHookMethod(Activity.class,</span><br><span class="line">            <span class="string">&quot;onCreate&quot;</span>, Bundle.class, <span class="keyword">new</span> <span class="title class_">XC_MethodHook</span>() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="type">void</span> <span class="title function_">afterHookedMethod</span><span class="params">(MethodHookParam param)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">                    <span class="built_in">super</span>.afterHookedMethod(param);</span><br><span class="line">                    <span class="type">Field</span> <span class="variable">mIntentField</span> <span class="operator">=</span> Activity.class.getDeclaredField(<span class="string">&quot;mIntent&quot;</span>);</span><br><span class="line">                    mIntentField.setAccessible(<span class="literal">true</span>);</span><br><span class="line">                    <span class="type">Intent</span> <span class="variable">intent</span> <span class="operator">=</span> (Intent) mIntentField.get(param.thisObject);</span><br><span class="line">                    LG.logE(intent.toUri(Intent.URI_INTENT_SCHEME));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后在assets目录下创建xposed_init文件，文件没有后缀名，内容就是上面这个接口的全类名</p><figure class="highlight txt"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">com.gloomyer.hook.Hook</span><br></pre></td></tr></table></figure><p>xposed应用本身不需要提供界面，但是一般插件如果需要动态配置些什么，那么最好你还是些一个界面。</p><p><font color='red'>注意：你提供的界面是在luncher中点开启动的activity，而xposed的代码运行在劫持进程中。所以代码虽然在一个项目中，但是如果需要通信，你需要走跨进程通信！（最简单的方式 发广播。）</font></p><p>上面的功能跑起来以后（记得去Xposed Manager中勾选你的插件，然后重启设备），你可以获取每个activity启动的参数了，如果对方没做什么安全检测。你就可以通过am命令启动任意activity了。</p><p>注意每次对插件代码上的改动，你都需要重启设备。</p>]]>
    </content>
    <id>https://gloomyer.com/2020/01/03/andoird_xposed_01/</id>
    <link href="https://gloomyer.com/2020/01/03/andoird_xposed_01/"/>
    <published>2020-01-03T09:31:32.000Z</published>
    <summary>Xposed框架(Xposed Framework)是一套开源的、在Android高权限模式下运行的框架服务，可以在不修改APK文件的情况下影响程序运行(修改系统)的框架服务，基于它可以制作出许多功能强大的模块，且在功能不冲突的情况下同时运</summary>
    <title>Android Xposed 模块开发</title>
    <updated>2020-01-03T09:31:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="差分升级" scheme="https://gloomyer.com/tags/%E5%B7%AE%E5%88%86%E5%8D%87%E7%BA%A7/"/>
    <category term="DownloadManager" scheme="https://gloomyer.com/tags/DownloadManager/"/>
    <content>
      <![CDATA[<blockquote><p>1.差分升级部署<br>2.更新的时候如何下载APK</p></blockquote><span id="more"></span><h2 id="差分升级部署"><a href="#差分升级部署" class="headerlink" title="差分升级部署"></a>差分升级部署</h2><p>初版项目提交，决定将差分升级引入项目，作为基础组件之一。</p><p>所以完整的一圈流程，包含服务端，客户端的实现与应用。</p><p>这里就不讲代码了，具体看这两个仓库<a href="https://github.com/Gloomyer/diff_android_library">Android客户端代码</a> <a href="https://github.com/Gloomyer/bsdiff_server">服务端端代码</a></p><p>服务端代码只支持2种系统（mac&#x2F;linux）</p><p>服务端代码想要跑起来需要安装bzlib</p><p><a href="/2017/06/26/difference_update_01/">差分实现原理</a></p><h2 id="DownloadManager"><a href="#DownloadManager" class="headerlink" title="DownloadManager"></a>DownloadManager</h2><h3 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h3><p>在APP更新中，我们一定要做的事情就是下载新版本的APK安装包。</p><p>那么如何下载？ 通常网上流传着大量的DownloadFileUtils。 一般调用okhttp去下载。</p><p>但是大部分都没有实现的一件事情：多线程下载。</p><p>多线程下载是非常有效的一个提升下载速度的方案，利用多个线程抢占CPU片段的方式来提升速度。</p><p>难道就没有使用多线程下载技术的DownloadUtils么？ 是有的，但是大部分都是一个library的方式提交给你。</p><p>但是实际上系统已经预置了一个下载管理器。</p><p>先说一下优缺点</p><table><thead><tr><th>功能</th><th>框架</th><th>系统提供的DownloadManager</th></tr></thead><tbody><tr><td>下载方案</td><td>下载做了一定的优化</td><td>由系统提供各种优化，小米rom下还会利用迅雷加速</td></tr><tr><td>引入大小</td><td>N个类，超多方法数</td><td>实际上是一种跨进程调用，只需要一个类提供对接即可</td></tr><tr><td>缺点</td><td>框架体积庞大&#x2F;方法数多&#x2F;如果下载过程中app被杀会导致apk下载失败</td><td>一个类，简单，不提供暂停&#x2F;继续下载功能，回调通知不灵敏，下载过程中用户可以去系统下载管理器暂停任务，但是我们没有办法继续下载</td></tr></tbody></table><p>DownloadManager 由我们通知他启动下载任务，完成活着失败 他会通过广播通知我们（也可以自己查询）</p><p>进度回调需要自己查询（没有框架灵敏 小米rom下他是每隔1s 才向内容提供者更新数据，我们只能通过内容提供者获取下载进度）。</p><h3 id="具体代码"><a href="#具体代码" class="headerlink" title="具体代码"></a>具体代码</h3><p>这里没有去注册广播监听完成，因为要做进度查询 已经可以获取到是否完成了。</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><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><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.app.DownloadManager;</span><br><span class="line"><span class="keyword">import</span> android.content.Context;</span><br><span class="line"><span class="keyword">import</span> android.database.Cursor;</span><br><span class="line"><span class="keyword">import</span> android.net.Uri;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Classname</span> DownloadFileUtils</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Description</span> 文件下载工具类</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Date</span> 2019-10-11 17:10</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@Created</span> by gloomy</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DownloadFileUtils</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Context mContext;</span><br><span class="line">    <span class="keyword">private</span> File path;</span><br><span class="line">    <span class="keyword">private</span> Callback callback;</span><br><span class="line">    <span class="keyword">private</span> DownloadManager downloadManager;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> downloadId;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 启动一个下载任务</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> context  上下</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> url      要下载的文件url</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> path     要保存的文件全路径</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> callback 回调（回调全部产生于子线程）</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">public</span> <span class="title function_">DownloadFileUtils</span><span class="params">(Context context,</span></span><br><span class="line"><span class="params">                             String url,</span></span><br><span class="line"><span class="params">                             File path,</span></span><br><span class="line"><span class="params">                             Callback callback)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.mContext = context.getApplicationContext();</span><br><span class="line">        <span class="built_in">this</span>.path = path;</span><br><span class="line">        <span class="built_in">this</span>.callback = callback;</span><br><span class="line">        downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);</span><br><span class="line">        downloadAPK(url, path);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">void</span> <span class="title function_">downloadAPK</span><span class="params">(String url, File path)</span> &#123;</span><br><span class="line">        DownloadManager.<span class="type">Request</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DownloadManager</span>.Request(Uri.parse(url));</span><br><span class="line">        request.setAllowedOverRoaming(<span class="literal">false</span>);</span><br><span class="line">        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);</span><br><span class="line">        request.setTitle(<span class="string">&quot;appName&quot;</span>);</span><br><span class="line">        request.setDescription(<span class="string">&quot;下载中...&quot;</span>);</span><br><span class="line">        request.setVisibleInDownloadsUi(<span class="literal">true</span>);</span><br><span class="line">        request.setDestinationUri(Uri.fromFile(path));</span><br><span class="line">        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI</span><br><span class="line">                | DownloadManager.Request.NETWORK_MOBILE);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//将下载请求加入下载队列，加入下载队列后会给该任务返回一个long型的id，通过该id可以取消任务，重启任务、获取下载的文件等等</span></span><br><span class="line">        <span class="keyword">if</span> (downloadManager != <span class="literal">null</span>) &#123;</span><br><span class="line">            downloadId = downloadManager.enqueue(request);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(queryRunner).start();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Runnable</span> <span class="variable">queryRunner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> status;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">long</span> soFar;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">long</span> total;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">int</span> progress;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> ((soFar &lt; total || total &lt;= <span class="number">0</span>)</span><br><span class="line">                    &amp;&amp; (status == DownloadManager.STATUS_RUNNING</span><br><span class="line">                    || status == DownloadManager.STATUS_PENDING</span><br><span class="line">                    || status == DownloadManager.STATUS_PAUSED</span><br><span class="line">                    || status == <span class="number">0</span>)) &#123;</span><br><span class="line">                query();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="type">void</span> <span class="title function_">query</span><span class="params">()</span> &#123;</span><br><span class="line">            DownloadManager.<span class="type">Query</span> <span class="variable">query</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DownloadManager</span>.Query();</span><br><span class="line">            query.setFilterById(downloadId);</span><br><span class="line">            <span class="type">Cursor</span> <span class="variable">cursor</span> <span class="operator">=</span> downloadManager.query(query);</span><br><span class="line">            <span class="keyword">if</span> (cursor.moveToNext()) &#123;</span><br><span class="line">                status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));</span><br><span class="line"></span><br><span class="line">                soFar = cursor.getLong(</span><br><span class="line">                        cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));</span><br><span class="line">                <span class="keyword">if</span> (total &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                    total = cursor.getLong(</span><br><span class="line">                            cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                progress = (<span class="type">int</span>) ((soFar * <span class="number">100f</span>) / total);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (status == DownloadManager.STATUS_PENDING) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (callback != <span class="literal">null</span>)</span><br><span class="line">                        callback.callback(<span class="number">0</span>, soFar, total, progress);</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (status == DownloadManager.STATUS_SUCCESSFUL</span><br><span class="line">                        || status == DownloadManager.STATUS_FAILED) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (callback != <span class="literal">null</span>)</span><br><span class="line">                        callback.callback(</span><br><span class="line">                                status == DownloadManager.STATUS_SUCCESSFUL</span><br><span class="line">                                        ? <span class="number">2</span> : <span class="number">3</span>,</span><br><span class="line">                                soFar, total, progress);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (callback != <span class="literal">null</span>)</span><br><span class="line">                        callback.callback(<span class="number">0</span>, soFar, total, progress);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                status = DownloadManager.STATUS_FAILED;</span><br><span class="line">                callback.callback(<span class="number">3</span>, soFar, total, progress);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span>(cursor != <span class="literal">null</span>) cursor.close();</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>); <span class="comment">//1秒才更新一次数据库，这里再小会查询到大量的重复数据</span></span><br><span class="line">            &#125; <span class="keyword">catch</span> (Exception ignored) &#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 下载回调</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Callback</span> &#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 下载回调</span></span><br><span class="line"><span class="comment">         *</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> what     0:开始下载 1:下载进度回调, 2:成功,3:失败</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> soFar    已经下载的字节数</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> total    文件大小</span></span><br><span class="line"><span class="comment">         * <span class="doctag">@param</span> progress 文件进度（0-100）</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="type">void</span> <span class="title function_">callback</span><span class="params">(<span class="type">int</span> what, <span class="type">long</span> soFar, <span class="type">long</span> total, <span class="type">int</span> progress)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2019/10/12/android_diff_server_and_downloadmanager/</id>
    <link href="https://gloomyer.com/2019/10/12/android_diff_server_and_downloadmanager/"/>
    <published>2019-10-12T09:31:32.000Z</published>
    <summary>初版项目提交，决定将差分升级引入项目，作为基础组件之一。所以完整的一圈流程，包含服务端，客户端的实现与应用。这里就不讲代码了，具体看这两个仓库Android客户端代码 服务端端代码</summary>
    <title>差分升级部署与 APK 下载</title>
    <updated>2019-10-12T09:31:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="Gradle" scheme="https://gloomyer.com/tags/Gradle/"/>
    <category term="打包" scheme="https://gloomyer.com/tags/%E6%89%93%E5%8C%85/"/>
    <category term="APK 加固" scheme="https://gloomyer.com/tags/APK-%E5%8A%A0%E5%9B%BA/"/>
    <content>
      <![CDATA[<blockquote><p>1.gradle 脚本中如何区分正式库与测试库<br>2.如何解决 Android多个渠道包自动加固问题</p></blockquote><span id="more"></span><h2 id="gradle-脚本中如何区分正式库与测试库"><a href="#gradle-脚本中如何区分正式库与测试库" class="headerlink" title="gradle 脚本中如何区分正式库与测试库"></a>gradle 脚本中如何区分正式库与测试库</h2><h3 id="为什么要区分"><a href="#为什么要区分" class="headerlink" title="为什么要区分?"></a>为什么要区分?</h3><p>是这样的，我们希望在测试库可以通过模拟器进行日常开发（需要x86架构支持）</p><p>但是正式库，我们希望对apk进行精简优化，不需要x86架构 只要armeabi-v7a</p><p>每次打包去修改非常麻烦， 特别是 如果打好包了又发现没有修改，那叫一个难受。</p><p>那么就希望在gradle中去检测正式库和测试库，进行不同的配置 </p><h3 id="如何区分"><a href="#如何区分" class="headerlink" title="如何区分"></a>如何区分</h3><p>直接上代码</p><figure class="highlight gradle"><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></pre></td><td class="code"><pre><span class="line">android&#123;</span><br><span class="line">    ...</span><br><span class="line">    defaultConfig &#123;</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">def</span> isRelease = <span class="keyword">false</span></span><br><span class="line">        <span class="keyword">for</span> (String s : gradle.startParameter.taskNames) &#123;</span><br><span class="line">            <span class="keyword">if</span> (s.contains(<span class="string">&quot;Release&quot;</span>) | s.contains(<span class="string">&quot;release&quot;</span>)) &#123;</span><br><span class="line">                isRelease = <span class="keyword">true</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 class="comment">//从本地local.properties中读取变量</span></span><br><span class="line">        <span class="keyword">def</span> properties = <span class="keyword">new</span> Properties()</span><br><span class="line">        <span class="keyword">def</span> inputStream = <span class="keyword">project</span>.rootProject.<span class="keyword">file</span>(<span class="string">&#x27;local.properties&#x27;</span>).newDataInputStream()</span><br><span class="line">        properties.load(inputStream)</span><br><span class="line"></span><br><span class="line">        ndk &#123;</span><br><span class="line">            <span class="comment">//默认不包含x86的so库，本地开发时如果要在模拟器上运行，可在local.properties文件中添加：</span></span><br><span class="line">            <span class="comment">//useX86=true</span></span><br><span class="line">            <span class="keyword">if</span> (properties.getProperty(<span class="string">&#x27;useX86&#x27;</span>) != <span class="keyword">null</span></span><br><span class="line">                    &amp;&amp; properties.getProperty(<span class="string">&#x27;useX86&#x27;</span>).toBoolean()</span><br><span class="line">                    &amp;&amp; !isRelease) &#123;</span><br><span class="line">                abiFilters <span class="string">&quot;armeabi-v7a&quot;</span>, <span class="string">&quot;x86&quot;</span></span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                abiFilters <span class="string">&quot;armeabi-v7a&quot;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="如何解决-Android多个渠道包自动加固问题"><a href="#如何解决-Android多个渠道包自动加固问题" class="headerlink" title="如何解决 Android多个渠道包自动加固问题"></a>如何解决 Android多个渠道包自动加固问题</h2><h3 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h3><p>这里不说代码了（太多了），说一下思路，下面提供github链接</p><p>首先通过配置 android gradle 实现多渠道包</p><p>然后用脚本去遍历app目录，获取生成好的apk文件。 </p><p>然后调用腾讯乐固提供的api，将文件上传到乐固进行加固，</p><p>遍历查询结果，ok之后下载加固包。</p><p>利用Android提供的签名工具进行对apk的重新签名。</p><p>这里说一下，因为乐固调用api不支持直接上传apk文件， 所以需要运维&#x2F;后端 在云存储方面提供支持。</p><p>我现在的公司用的是七牛的服务，运维专门给我开了一个bocket，用来存储apk文件。</p><p>所以为了加固，我会将未加固的apk上传到七牛。</p><p>为了方便给测试和产品给包。我也会将加固签名好后的apk包传到七牛云，最后还会将apk的实际下载地址生成二维码，方便他们下载。</p><p>具体的使用方法，在github 里面有，这里就不详细描述了</p><h3 id="github地址"><a href="#github地址" class="headerlink" title="github地址"></a>github地址</h3><p><a href="https://github.com/Gloomyer/AutoReinforce">android 自动打包 自动加固工具</a></p>]]>
    </content>
    <id>https://gloomyer.com/2019/09/20/android_auto_reinforce/</id>
    <link href="https://gloomyer.com/2019/09/20/android_auto_reinforce/"/>
    <published>2019-09-20T09:31:32.000Z</published>
    <summary>是这样的，我们希望在测试库可以通过模拟器进行日常开发（需要x86架构支持），但是正式库，我们希望对apk进行精简优化，不需要x86架构 只要armeabi-v7a</summary>
    <title>Gradle 区分正式/测试库与多渠道包自动加固</title>
    <updated>2019-09-20T09:31:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="Bitmap" scheme="https://gloomyer.com/tags/Bitmap/"/>
    <category term="JNI" scheme="https://gloomyer.com/tags/JNI/"/>
    <content>
      <![CDATA[<blockquote><p>Android 识别图片亮度</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>有一个界面是文字盖在图片上面，</p><p>设计希望能够更具图片的亮度来让文字自动设置为白色或者黑色。</p><p>所以就需要识别图片的light值。</p><p>需要依赖jni开发技术</p><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>java层拿到bitmap，然后丢给c++处理。</p><p>bitmap的格式通常都为rgba8888</p><p>我们将rgba8888转换为yuv，拿到Y值 y就是亮度，然后求平均值即可得到图片整体的亮度</p><p>第一版目前是这样，但是这样是有一定误差的，比如一张顶部1&#x2F;3纯白 底部2&#x2F;3纯黑的图片，会被认为是暗得，但是如果你的文字是盖在上部分，那么误差就会出现。</p><p>具体可以修改不求整体的平均值，求图片的局部亮度平均值更为精准。</p><p>图片的识别是一件耗时操作（虽然也很快），但是还是推荐在创建的时候去识别，服务端记录，展示的时候直接根据字段计算即可。</p><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><p>AS创建JNI项目，</p><p>移动CMakeLists至app目录下</p><p>CMakeLists内容:</p><figure class="highlight cmake"><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"><span class="keyword">cmake_minimum_required</span>(VERSION <span class="number">3.4</span>.<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_library</span>(libimg SHARED src/main/cpp/native-lib.cpp)</span><br><span class="line"></span><br><span class="line"><span class="keyword">find_library</span>(log-lib log)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_link_libraries</span>(libimg jnigraphics <span class="variable">$&#123;log-lib&#125;</span>)</span><br></pre></td></tr></table></figure><p>build.gradle修改</p><figure class="highlight groovy"><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">externalNativeBuild &#123;</span><br><span class="line">  cmake &#123;</span><br><span class="line">    path <span class="string">&quot;CMakeLists.txt&quot;</span></span><br><span class="line">version <span class="string">&quot;3.10.2&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>MainActivity代码:</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.gloomyer.lightdemo;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> android.Manifest;</span><br><span class="line"><span class="keyword">import</span> android.graphics.Bitmap;</span><br><span class="line"><span class="keyword">import</span> android.graphics.BitmapFactory;</span><br><span class="line"><span class="keyword">import</span> android.os.Environment;</span><br><span class="line"><span class="keyword">import</span> android.support.v4.app.ActivityCompat;</span><br><span class="line"><span class="keyword">import</span> android.support.v7.app.AppCompatActivity;</span><br><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> android.util.Log;</span><br><span class="line"><span class="keyword">import</span> android.view.View;</span><br><span class="line"><span class="keyword">import</span> android.widget.ImageView;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainActivity</span> <span class="keyword">extends</span> <span class="title class_">AppCompatActivity</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TAG</span> <span class="operator">=</span> <span class="string">&quot;MainActivity&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.loadLibrary(<span class="string">&quot;libimg&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> ImageView iv;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line">        ActivityCompat.requestPermissions(<span class="built_in">this</span>, <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;Manifest.permission.READ_EXTERNAL_STORAGE&#125;, <span class="number">123</span>);</span><br><span class="line">        iv = findViewById(R.id.iv);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">getLight</span><span class="params">(Bitmap bitmap)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isLight</span><span class="params">(Bitmap bitmap)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> getLight(bitmap) &gt; <span class="number">128</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">load</span><span class="params">(View view)</span> &#123;</span><br><span class="line"><span class="comment">//        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.demo);</span></span><br><span class="line">        <span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> BitmapFactory.decodeFile(<span class="keyword">new</span> <span class="title class_">File</span>(Environment.getExternalStorageDirectory(), <span class="string">&quot;black.jpeg&quot;</span>).getAbsolutePath());</span><br><span class="line">        <span class="keyword">if</span> (bitmap == <span class="literal">null</span>) &#123;</span><br><span class="line">            Log.e(TAG, <span class="string">&quot;bitmap  == null&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        Log.e(TAG, <span class="string">&quot;is light:&quot;</span> + isLight(bitmap));</span><br><span class="line">        iv.setImageBitmap(bitmap);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>native-lib.cpp代码</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><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="meta">#<span class="keyword">include</span> <span class="string">&lt;jni.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;android/log.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;android/bitmap.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,<span class="string">&quot;MainActivity&quot;</span>,FORMAT,##__VA_ARGS__);</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL</span></span><br><span class="line"><span class="function"><span class="title">Java_com_gloomyer_lightdemo_MainActivity_getLight</span><span class="params">(JNIEnv *env, jobject instance, jobject bitmap)</span> </span>&#123;</span><br><span class="line">    AndroidBitmapInfo info;</span><br><span class="line">    <span class="type">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> ((ret = <span class="built_in">AndroidBitmap_getInfo</span>(env, bitmap, &amp;info)) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">uint32_t</span> width = info.height;</span><br><span class="line">        <span class="type">uint32_t</span> height = info.width;</span><br><span class="line">        <span class="built_in">LOGE</span>(<span class="string">&quot;width:%d, height:%d, format:%d&quot;</span>, info.width, info.height, info.format);</span><br><span class="line">        <span class="type">void</span> *addr = <span class="keyword">new</span> <span class="type">uint32_t</span>[width * height];</span><br><span class="line">        <span class="type">long</span> whereToGet = <span class="number">0</span>, bright = <span class="number">0</span>;</span><br><span class="line">        <span class="type">int</span> r, g, b;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">AndroidBitmap_lockPixels</span>(env, bitmap, &amp;addr) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; width; i++) &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; height; j++) &#123;</span><br><span class="line">                    <span class="type">uint32_t</span> pixel = ((<span class="type">uint32_t</span> *) addr)[whereToGet++];</span><br><span class="line">                    r = (pixel | <span class="number">0xff00ffff</span>) &gt;&gt; <span class="number">16</span> &amp; <span class="number">0x00ff</span>;</span><br><span class="line">                    g = (pixel | <span class="number">0xffff00ff</span>) &gt;&gt; <span class="number">8</span> &amp; <span class="number">0x0000ff</span>;</span><br><span class="line">                    b = (pixel | <span class="number">0xffffff00</span>) &amp; <span class="number">0x0000ff</span>;</span><br><span class="line">                    bright = (<span class="type">int</span>) (bright + <span class="number">0.299</span> * r + <span class="number">0.587</span> * g + <span class="number">0.114</span> * b);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">AndroidBitmap_unlockPixels</span>(env, bitmap);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="built_in">LOGE</span>(<span class="string">&quot;ret:%d&quot;</span>, ret);</span><br><span class="line">        <span class="keyword">return</span> bright / (whereToGet - <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="GitHub下载"><a href="#GitHub下载" class="headerlink" title="GitHub下载"></a>GitHub下载</h2><p><a href="https://github.com/Gloomyer/android_distinguish_image_light"><strong>android_distinguish_image_light</strong></a></p>]]>
    </content>
    <id>https://gloomyer.com/2019/06/04/android_image_distinguish_light/</id>
    <link href="https://gloomyer.com/2019/06/04/android_image_distinguish_light/"/>
    <published>2019-06-04T09:31:32.000Z</published>
    <summary>有一个界面是文字盖在图片上面，设计希望能够更具图片的亮度来让文字自动设置为白色或者黑色。所以就需要识别图片的light值。需要依赖jni开发技术，java层拿到bitmap，然后丢给c++处理。</summary>
    <title>Android 识别图片亮度</title>
    <updated>2019-06-04T09:31:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="音视频" scheme="https://gloomyer.com/categories/%E9%9F%B3%E8%A7%86%E9%A2%91/"/>
    <category term="NDK" scheme="https://gloomyer.com/tags/NDK/"/>
    <category term="音频" scheme="https://gloomyer.com/tags/%E9%9F%B3%E9%A2%91/"/>
    <category term="Speex" scheme="https://gloomyer.com/tags/Speex/"/>
    <content>
      <![CDATA[<blockquote><p>编译Speexdsp</p><p>在Android平台进行降噪&#x2F;回声消除处理</p><span id="more"></span></blockquote><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p><em>Speex</em>是一套主要针对语音的开源免费，无专利保护的音频压缩格式</p><p>SpeexDsp是一套主要针对语音的噪音屏蔽&#x2F;回声消除的处理框架.</p><p>我们这里主要讲speexdsp 目前最新版本是1.2rc3</p><p><a href="http://downloads.xiph.org/releases/speex/speexdsp-1.2rc3.tar.gz">官网speexdsp1.2rc3下载</a></p><p><a href="www.speex.org">speex官网 www.speex.org</a></p><h2 id="编译speexdsp"><a href="#编译speexdsp" class="headerlink" title="编译speexdsp"></a>编译speexdsp</h2><p>需要准备</p><ul><li><p>ndk</p></li><li><p>Linux&#x2F;Mac系统</p></li><li><p>speexdsp源码</p></li></ul><p>首先下载源码，解压之后进入到源码根目录执行</p><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">./configure</span><br></pre></td></tr></table></figure><p>等待一会 等待speexdsp 配置完成。</p><p>然后在speexdsp根目录创建Android.mk文件 内容如下</p><figure class="highlight makefile"><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">LOCAL_PATH := <span class="variable">$(<span class="built_in">call</span> my-<span class="built_in">dir</span>)</span></span><br><span class="line"><span class="keyword">include</span> <span class="variable">$(CLEAR_VARS)</span></span><br><span class="line">LOCAL_MODULE    := libspeexdsp</span><br><span class="line">LOCAL_CFLAGS = -DFIXED_POINT -DUSE_KISS_FFT -DEXPORT=<span class="string">&quot;&quot;</span> -DHAVE_STDINT_H -UHAVE_CONFIG_H</span><br><span class="line">LOCAL_C_INCLUDES := <span class="variable">$(LOCAL_PATH)</span>/<span class="keyword">include</span></span><br><span class="line">LOCAL_LDLIBS := -llog</span><br><span class="line">LOCAL_SRC_FILES :=  \</span><br><span class="line">./libspeexdsp/buffer.c \</span><br><span class="line">./libspeexdsp/fftwrap.c \</span><br><span class="line">./libspeexdsp/filterbank.c \</span><br><span class="line">./libspeexdsp/jitter.c \</span><br><span class="line">./libspeexdsp/kiss_fft.c \</span><br><span class="line">./libspeexdsp/kiss_fftr.c \</span><br><span class="line">./libspeexdsp/mdf.c \</span><br><span class="line">./libspeexdsp/preprocess.c \</span><br><span class="line">./libspeexdsp/resample.c \</span><br><span class="line">./libspeexdsp/scal.c \</span><br><span class="line">./libspeexdsp/smallft.c</span><br><span class="line"></span><br><span class="line"><span class="keyword">include</span> <span class="variable">$(BUILD_SHARED_LIBRARY)</span></span><br><span class="line"><span class="comment">#include $(BUILD_STATIC_LIBRARY)</span></span><br></pre></td></tr></table></figure><p>继续创建Application.mk</p><figure class="highlight makefile"><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">APP_PLATFORM = android-16</span><br><span class="line">APP_ABI := all</span><br></pre></td></tr></table></figure><p>这里说明一下，如果你需要打armeabi的包 那么这里的最小版本需要修改到9 然后你的ndk也不能特别的新 可以考虑r10</p><p>然后执行build命令(需要配置好ndk)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk APP_BUILD_SCRIPT=Android.mk</span><br></pre></td></tr></table></figure><p>我这里用的是19 最新的ndk需要指定个mk文件 老版本直接ndk-build即可</p><h2 id="配置开发环境"><a href="#配置开发环境" class="headerlink" title="配置开发环境"></a>配置开发环境</h2><p>将speexdsp include目录copy到cpp开发目录下</p><p>将libs生成的so copy 到jniLibs目录下</p><p>修改speexdsp_config_types.h文件</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="keyword">ifndef</span> __SPEEX_TYPES_H__</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __SPEEX_TYPES_H__</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;stdint.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int16_t</span> <span class="type">spx_int16_t</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">uint16_t</span> <span class="type">spx_uint16_t</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int32_t</span> <span class="type">spx_int32_t</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">uint32_t</span> <span class="type">spx_uint32_t</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>删除.mk .in 多余文件</p><p>将CMakeLists.txt移动至app目录下</p><p>修改build.gradle</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">android&#123;</span><br><span class="line">...</span><br><span class="line"> externalNativeBuild &#123;</span><br><span class="line">        cmake &#123;</span><br><span class="line">            path &quot;CMakeLists.txt&quot;</span><br><span class="line">            version &quot;3.10.2&quot;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>修改CMakeLists.txt内容</p><figure class="highlight cmake"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">cmake_minimum_required</span>(VERSION <span class="number">3.4</span>.<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_library</span>(myspeex SHARED src/main/cpp/myspeex.cpp)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_include_directories</span>(myspeex PRIVATE <span class="variable">$&#123;CMAKE_SOURCE_DIR&#125;</span>/src/main/cpp/<span class="keyword">include</span>/)</span><br><span class="line"></span><br><span class="line"><span class="keyword">add_library</span>(speexdsp SHARED IMPORTED)</span><br><span class="line"><span class="keyword">set_target_properties</span>(speexdsp PROPERTIES IMPORTED_LOCATION <span class="variable">$&#123;CMAKE_SOURCE_DIR&#125;</span>/src/main/jniLibs/<span class="variable">$&#123;CMAKE_ANDROID_ARCH_ABI&#125;</span>/libspeexdsp.so)</span><br><span class="line"></span><br><span class="line"><span class="keyword">find_library</span>(log-lib log)</span><br><span class="line"></span><br><span class="line"><span class="keyword">target_link_libraries</span>(myspeex <span class="variable">$&#123;log-lib&#125;</span> speexdsp)</span><br></pre></td></tr></table></figure><h2 id="实现降噪"><a href="#实现降噪" class="headerlink" title="实现降噪"></a>实现降噪</h2><h3 id="java层代码"><a href="#java层代码" class="headerlink" title="java层代码"></a>java层代码</h3><p>创建SpeexJNIBridge.java</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><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpeexJNIBridge</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.loadLibrary(<span class="string">&quot;speexdsp&quot;</span>);</span><br><span class="line">        System.loadLibrary(<span class="string">&quot;myspeex&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">void</span> <span class="title function_">init</span><span class="params">(<span class="type">int</span> frame_size, <span class="type">int</span> sampling_rate)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">void</span> <span class="title function_">destory</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">denoise</span><span class="params">(<span class="type">byte</span>[] buffer)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 回声消除处理</span></span><br><span class="line"><span class="comment">     * 这个方法包含降噪处理，无需在单独调用</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> inBuffer   本地录制的声音(准备发出去的)</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> playBuffer 本地准备播放的声音(接受到的)</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 处理好的数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">byte</span>[] <span class="title function_">cancellation</span><span class="params">(<span class="type">byte</span>[] inBuffer, <span class="type">byte</span>[] playBuffer)</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>准备利用AudioRecord录制声音</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainActivity</span> <span class="keyword">extends</span> <span class="title class_">AppCompatActivity</span> &#123;</span><br><span class="line">   <span class="comment">//来源：麦克风</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">AUDIO_INPUT</span> <span class="operator">=</span> MediaRecorder.AudioSource.MIC;</span><br><span class="line">    <span class="comment">// 采样率</span></span><br><span class="line">    <span class="comment">// 44100是目前的标准，但是某些设备仍然支持22050，16000，11025</span></span><br><span class="line">    <span class="comment">// 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">AUDIO_SAMPLE_RATE</span> <span class="operator">=</span> <span class="number">44100</span>;</span><br><span class="line">    <span class="comment">// 音频通道 单声道</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">AUDIO_CHANNEL</span> <span class="operator">=</span> AudioFormat.CHANNEL_IN_STEREO;</span><br><span class="line">    <span class="comment">// 音频格式：PCM编码</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">AUDIO_FORMAT</span> <span class="operator">=</span> AudioFormat.ENCODING_PCM_16BIT;</span><br><span class="line">  </span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 是否启用jni层处理功能</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> denoise</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">void</span> <span class="title function_">record</span><span class="params">(<span class="type">boolean</span> denoise)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (isRecord) &#123;</span><br><span class="line">            Toast.makeText(<span class="built_in">this</span>, <span class="string">&quot;请先停止&quot;</span>, Toast.LENGTH_SHORT).show();</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        bufferSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,</span><br><span class="line">                AUDIO_CHANNEL, AUDIO_FORMAT);</span><br><span class="line">        mAudioRecord = <span class="keyword">new</span> <span class="title class_">AudioRecord</span>(AUDIO_INPUT, AUDIO_SAMPLE_RATE,</span><br><span class="line">                AUDIO_CHANNEL, AUDIO_FORMAT, bufferSize);</span><br><span class="line">        mAudioTrack = <span class="keyword">new</span> <span class="title class_">AudioTrack</span>(AudioManager.STREAM_MUSIC, AUDIO_SAMPLE_RATE,</span><br><span class="line">                AUDIO_CHANNEL, AUDIO_FORMAT, bufferSize, AudioTrack.MODE_STREAM);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (denoise) &#123;</span><br><span class="line">            SpeexJNIBridge.init(bufferSize, AUDIO_SAMPLE_RATE);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        buffer = <span class="keyword">new</span> <span class="title class_">byte</span>[bufferSize];</span><br><span class="line">        <span class="keyword">if</span> (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) &#123;</span><br><span class="line">            mAudioRecord = <span class="literal">null</span>;</span><br><span class="line">            Toast.makeText(<span class="built_in">this</span>, <span class="string">&quot;初始化失败!&quot;</span>, Toast.LENGTH_SHORT).show();</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        mAudioRecord.startRecording();</span><br><span class="line">        mAudioTrack.play();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">RecordTask</span>(denoise)).start();</span><br><span class="line">    &#125;</span><br><span class="line">  </span><br><span class="line">   <span class="keyword">private</span> <span class="keyword">class</span> <span class="title class_">RecordTask</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">boolean</span> denoise;</span><br><span class="line"></span><br><span class="line">        <span class="type">public</span> <span class="title function_">RecordTask</span><span class="params">(<span class="type">boolean</span> denoise)</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.denoise = denoise;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            Thread.currentThread().setPriority(Thread.MAX_PRIORITY);</span><br><span class="line">            isRecord = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">while</span> (isRecord) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">read</span> <span class="operator">=</span> mAudioRecord.read(buffer, <span class="number">0</span>, bufferSize);</span><br><span class="line">                <span class="keyword">if</span> (read &gt;= <span class="number">2</span>) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (denoise) &#123;</span><br><span class="line">                        SpeexJNIBridge.denoise(buffer);</span><br><span class="line">                    &#125;</span><br><span class="line">                    mAudioTrack.write(buffer, <span class="number">0</span>, read);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (denoise)</span><br><span class="line">                SpeexJNIBridge.destory();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里说一下，因为这是demo，不涉及到发送接收数据，这里没办法测试回声消除（会导致自己的声音消失）</p><h3 id="c-c-代码"><a href="#c-c-代码" class="headerlink" title="c&#x2F;c++代码"></a>c&#x2F;c++代码</h3><p>就一个文件</p><p>myspeex.cpp</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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;jni.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;speex/speex_preprocess.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;speex/speex_echo.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;android/log.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LOGE(...) __android_log_print(ANDROID_LOG_ERROR, <span class="string">&quot;gloomy&quot;</span>, __VA_ARGS__)</span></span><br><span class="line"></span><br><span class="line">SpeexPreprocessState *state = <span class="literal">nullptr</span>;</span><br><span class="line">SpeexEchoState *echoState = <span class="literal">nullptr</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span></span><br><span class="line"><span class="function">JNIEXPORT <span class="type">void</span> JNICALL</span></span><br><span class="line"><span class="function"><span class="title">Java_com_gloomyer_myspeex_interfaces_SpeexJNIBridge_init</span><span class="params">(JNIEnv *env, jclass type,</span></span></span><br><span class="line"><span class="params"><span class="function">                                                         jint frame_size, jint sampling_rate)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    frame_size /= <span class="number">2</span>;</span><br><span class="line">    <span class="type">int</span> filterLength = frame_size * <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//降噪初始化</span></span><br><span class="line">    state = <span class="built_in">speex_preprocess_state_init</span>(frame_size, sampling_rate);</span><br><span class="line">    <span class="type">int</span> i = <span class="number">1</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_DENOISE, &amp;i); <span class="comment">//降噪 建议1</span></span><br><span class="line">    i = <span class="number">-25</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &amp;i);<span class="comment">////设置噪声的dB</span></span><br><span class="line">    i = <span class="number">1</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_AGC, &amp;i);<span class="comment">////增益</span></span><br><span class="line">    i = <span class="number">24000</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_AGC_LEVEL, &amp;i);</span><br><span class="line">    i = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_DEREVERB, &amp;i);</span><br><span class="line">    <span class="type">float</span> f = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &amp;f);</span><br><span class="line">    f = <span class="number">0</span>;</span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &amp;f);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//回音消除初始化</span></span><br><span class="line">    echoState = <span class="built_in">speex_echo_state_init</span>(frame_size, filterLength);</span><br><span class="line">    <span class="built_in">speex_echo_ctl</span>(echoState, SPEEX_ECHO_SET_SAMPLING_RATE, &amp;sampling_rate);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//关联</span></span><br><span class="line">    <span class="built_in">speex_preprocess_ctl</span>(state, SPEEX_PREPROCESS_SET_ECHO_STATE, echoState);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span></span><br><span class="line"><span class="function">JNIEXPORT <span class="type">void</span> JNICALL</span></span><br><span class="line"><span class="function"><span class="title">Java_com_gloomyer_myspeex_interfaces_SpeexJNIBridge_destory</span><span class="params">(JNIEnv *env, jclass type)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (state) &#123;</span><br><span class="line">        <span class="built_in">speex_preprocess_state_destroy</span>(state);</span><br><span class="line">        state = <span class="literal">nullptr</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (echoState) &#123;</span><br><span class="line">        <span class="built_in">speex_echo_state_destroy</span>(echoState);</span><br><span class="line">        echoState = <span class="literal">nullptr</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="keyword">extern</span> <span class="string">&quot;C&quot;</span></span><br><span class="line"><span class="function">JNIEXPORT jint JNICALL</span></span><br><span class="line"><span class="function"><span class="title">Java_com_gloomyer_myspeex_interfaces_SpeexJNIBridge_denoise</span><span class="params">(JNIEnv *env, jclass jcls,</span></span></span><br><span class="line"><span class="params"><span class="function">                                                            jbyteArray buffer_)</span> </span>&#123;</span><br><span class="line">    jbyte *in_buffer = env-&gt;<span class="built_in">GetByteArrayElements</span>(buffer_, <span class="literal">nullptr</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> *in = (<span class="type">short</span> *) in_buffer;</span><br><span class="line">    <span class="type">int</span> rcode = <span class="built_in">speex_preprocess_run</span>(state, in);</span><br><span class="line"></span><br><span class="line">    env-&gt;<span class="built_in">ReleaseByteArrayElements</span>(buffer_, in_buffer, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> rcode;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="string">&quot;C&quot;</span></span><br><span class="line"><span class="function">JNIEXPORT jbyteArray JNICALL</span></span><br><span class="line"><span class="function"><span class="title">Java_com_gloomyer_myspeex_interfaces_SpeexJNIBridge_cancellation</span><span class="params">(JNIEnv *env, jclass jcls,</span></span></span><br><span class="line"><span class="params"><span class="function">                                                                 jbyteArray inBuffer_,</span></span></span><br><span class="line"><span class="params"><span class="function">                                                                 jbyteArray playBuffer_)</span> </span>&#123;</span><br><span class="line">    jbyte *inBuffer = env-&gt;<span class="built_in">GetByteArrayElements</span>(inBuffer_, <span class="literal">nullptr</span>);</span><br><span class="line">    jbyte *playBuffer = env-&gt;<span class="built_in">GetByteArrayElements</span>(playBuffer_, <span class="literal">nullptr</span>);</span><br><span class="line"></span><br><span class="line">    jsize length = env-&gt;<span class="built_in">GetArrayLength</span>(playBuffer_);</span><br><span class="line">    jbyteArray retBuffer_ = env-&gt;<span class="built_in">NewByteArray</span>(length);</span><br><span class="line">    jbyte *retBuffer = env-&gt;<span class="built_in">GetByteArrayElements</span>(retBuffer_, <span class="literal">nullptr</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">auto</span> *in = (<span class="type">short</span> *) inBuffer;</span><br><span class="line">    <span class="keyword">auto</span> *play = (<span class="type">short</span> *) playBuffer;</span><br><span class="line">    <span class="keyword">auto</span> *ret = (<span class="type">short</span> *) retBuffer;</span><br><span class="line">    <span class="built_in">speex_echo_cancellation</span>(echoState, in, play, ret); <span class="comment">//消除回声</span></span><br><span class="line">    <span class="built_in">speex_preprocess_run</span>(state, ret);</span><br><span class="line"></span><br><span class="line">    env-&gt;<span class="built_in">ReleaseByteArrayElements</span>(inBuffer_, inBuffer, <span class="number">0</span>);</span><br><span class="line">    env-&gt;<span class="built_in">ReleaseByteArrayElements</span>(playBuffer_, playBuffer, <span class="number">0</span>);</span><br><span class="line">    env-&gt;<span class="built_in">ReleaseByteArrayElements</span>(retBuffer_, retBuffer, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> retBuffer_;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="代码下载"><a href="#代码下载" class="headerlink" title="代码下载"></a>代码下载</h2><p><a href="https://github.com/Gloomyer/speexdsp_android_demo">github浏览下载</a></p>]]>
    </content>
    <id>https://gloomyer.com/2019/04/29/android_speexdsp/</id>
    <link href="https://gloomyer.com/2019/04/29/android_speexdsp/"/>
    <published>2019-04-29T09:31:32.000Z</published>
    <summary>Speex是一套主要针对语音的开源免费，无专利保护的音频压缩格式，SpeexDsp是一套主要针对语音的噪音屏蔽/回声消除的处理框架.我们这里主要讲speexdsp 目前最新版本是1.2rc3</summary>
    <title>Android 编译 SpeexDSP 实现降噪/回声消除</title>
    <updated>2019-04-29T09:31:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="音视频" scheme="https://gloomyer.com/categories/%E9%9F%B3%E8%A7%86%E9%A2%91/"/>
    <category term="NDK" scheme="https://gloomyer.com/tags/NDK/"/>
    <category term="ijkplayer" scheme="https://gloomyer.com/tags/ijkplayer/"/>
    <category term="FFmpeg" scheme="https://gloomyer.com/tags/FFmpeg/"/>
    <content>
      <![CDATA[<blockquote><p>Android 编译IJKPlayer0.8.8 支持rtmp https</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>ijk官方提供的lib 是不支持https&#x2F;rtmp 直播流的 播放</p><p>当然ijk是支持的 需要我们下载源码 修改参数 手动编译即可实现。</p><p>末尾提供懒人包下载</p><h3 id="环境"><a href="#环境" class="headerlink" title="环境"></a>环境</h3><p>ijk源码(0.8.8)</p><blockquote><p>git clone <a href="https://github.com/bilibili/ijkplayer.git">https://github.com/bilibili/ijkplayer.git</a></p></blockquote><p>ndkr10e</p><blockquote><p>官网写的是r10e 我用r19 失败了 但是网上有人说貌似13 14是成功的，看你自己了 省事直接用r10e</p><p><a href="https://blog.csdn.net/shuzfan/article/details/52690554">NDKr10e下载地址</a></p></blockquote><p>系统</p><blockquote><p>官网说是ubuntu，我用的mac成功了。windows没试过。</p></blockquote><p>git</p><blockquote><p>不用多说了吧</p></blockquote><h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a>开始</h2><p>准备好了就可以开始编译了</p><h3 id="配置环境变量"><a href="#配置环境变量" class="headerlink" title="配置环境变量"></a>配置环境变量</h3><p>ANDROID_NDK</p><blockquote><p>指向ndk目录</p></blockquote><p>ANDROID_SDK</p><blockquote><p>指向sdk目录</p></blockquote><p>没个系统环境变量配置有所不同 自己根据自己系统来就好</p><h3 id="下载ijk源码"><a href="#下载ijk源码" class="headerlink" title="下载ijk源码"></a>下载ijk源码</h3><blockquote><p>git clone <a href="https://github.com/bilibili/ijkplayer.git">https://github.com/bilibili/ijkplayer.git</a></p></blockquote><h3 id="修改要支持的协议"><a href="#修改要支持的协议" class="headerlink" title="修改要支持的协议"></a>修改要支持的协议</h3><figure class="highlight shell"><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">cd ijkplayer</span><br><span class="line">cd config</span><br></pre></td></tr></table></figure><blockquote><p>module-default.sh   module-lite-hevc.sh module-lite.sh      module.sh</p></blockquote><p>module-default.sh</p><blockquote><p>较多的格式支持 编译之后文件体积过大（但是我没在里面找到rtmp??）</p></blockquote><p>module-lite-hevc.sh</p><blockquote><p>较少的格式支持 编译之后文件体积最小 不推荐使用</p></blockquote><p>module-lite.sh</p><blockquote><p>默认的格式支持 我是直接使用这个改了改</p></blockquote><p>module.sh</p><blockquote><p>ijk编译使用哪个 这是一个链接文件指向module-lite.sh</p></blockquote><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">vim module-lite.sh</span><br></pre></td></tr></table></figure><p>搜索rtm 找到几个 如下</p><blockquote><p>export COMMON_FF_CFG_FLAGS&#x3D;”$COMMON_FF_CFG_FLAGS –enable-protocol&#x3D;rtmp*”</p><p>export COMMON_FF_CFG_FLAGS&#x3D;”$COMMON_FF_CFG_FLAGS –enable-protocol&#x3D;rtmp”</p><p>export COMMON_FF_CFG_FLAGS&#x3D;”$COMMON_FF_CFG_FLAGS –enable-protocol&#x3D;rtmpt”</p></blockquote><p>如果前面有disabled的 修改为enable即可</p><p>###初始化依赖文件</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">回到ijk根目录</span></span><br><span class="line">cd ..</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">这是支持https</span></span><br><span class="line">./init-android-openssl.sh</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">这是Android平台使用</span></span><br><span class="line">./init-android.sh</span><br></pre></td></tr></table></figure><h3 id="开始编译"><a href="#开始编译" class="headerlink" title="开始编译"></a>开始编译</h3><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></pre></td><td class="code"><pre><span class="line">cd android/contrib</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">清除缓存</span></span><br><span class="line">./compile-openssl.sh clean</span><br><span class="line">./compile-ffmpeg.sh clean</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">开始编译依赖库</span></span><br><span class="line">./compile-openssl.sh all</span><br><span class="line">./compile-ffmpeg.sh all</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">回到ijk根目录/android/</span></span><br><span class="line">cd ..</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">开始编译 all表示全平台 如果只要某个平台 修改为某个平台即可</span></span><br><span class="line">./compile-ijk.sh all</span><br></pre></td></tr></table></figure><h3 id="完成"><a href="#完成" class="headerlink" title="完成"></a>完成</h3><p>生成好的文件在</p><blockquote><p>ijkplayer&#x2F;android&#x2F;ijkplayer</p></blockquote><p>ijkplayer-java tools</p><p>这两个目录必须 其他的就是cpu架构支持目录 根据需要选择即可</p><h2 id="懒人包下载"><a href="#懒人包下载" class="headerlink" title="懒人包下载"></a>懒人包下载</h2><p><a href="https://pan.baidu.com/s/1aDNGNb283RlBG0Yjb9PwmQ">ijk0.8.8 支持rtmp https 懒人包</a></p><p>密码:w9nc</p>]]>
    </content>
    <id>https://gloomyer.com/2019/04/15/android_build_ijk_support_rtmp_https/</id>
    <link href="https://gloomyer.com/2019/04/15/android_build_ijk_support_rtmp_https/"/>
    <published>2019-04-15T09:31:32.000Z</published>
    <summary>ijk官方提供的lib 是不支持https/rtmp 直播流的 播放，当然ijk是支持的 需要我们下载源码 修改参数 手动编译即可实现。末尾提供懒人包下载，ijk源码(0.8.8)</summary>
    <title>Android 编译 IJKPlayer 0.8.8 支持 RTMP/HTTPS</title>
    <updated>2019-04-15T09:31:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="安全与逆向" scheme="https://gloomyer.com/categories/%E5%AE%89%E5%85%A8%E4%B8%8E%E9%80%86%E5%90%91/"/>
    <category term="Windows" scheme="https://gloomyer.com/tags/Windows/"/>
    <category term="PE" scheme="https://gloomyer.com/tags/PE/"/>
    <content>
      <![CDATA[<blockquote><p>Windows PE 结构学习</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p><img src="/img/img/pe_struct.jpg" alt="完整PE结构图"></p><p>今天主要讲解什么是节&#x2F;数据目录&#x2F;rva 是什么</p><h3 id="节表和节"><a href="#节表和节" class="headerlink" title="节表和节"></a>节表和节</h3><h4 id="Windows如何将PE文件映射至内存"><a href="#Windows如何将PE文件映射至内存" class="headerlink" title="Windows如何将PE文件映射至内存"></a>Windows如何将PE文件映射至内存</h4><p>在执行&#x2F;加载一个PE文件的时候,系统并不是开始就将整个文件并入内存,而是采用映射关系机制.PE装载器(系统)只建立号虚拟内存地址(RVA)和PE文件的映射关系.而只有当真正执行到某个内存页的指令或者访问某一页的数据时,这个页面才会从磁盘提交到物理内存.</p><p>系统装载可执行文件的方法又不完全等同于内存映射文件。当使用内存映射文件的时候，系统对“原著”相当忠实，如果将磁盘文件和内存映像比较的话，可以发现不管是数据本身还是数据之间的相对位置的都是完全相同的。而我们知道，在装载可执行文件的时候，有些数据在装入前会被预处理，如重定位等，正因此，装入以后，数据之间的相对位置可能发生微妙的变化。</p><p>操作系统还有内存隔离的特性,比如一个电脑是4GB内存,那么在每一个被装载进内存的程序看来.都是自己独享这4GB的内存.所以在EXE程序的执行,内存基址基本上总是装载在ImageBase(建议内存装载地址 vc一般为40000).dll 因为一个程序可能加载N个dll 所以dll 的建议装载地址未必生效.</p><h4 id="文件镜像内存镜像加载对照图"><a href="#文件镜像内存镜像加载对照图" class="headerlink" title="文件镜像&lt;-&gt;内存镜像加载对照图"></a>文件镜像&lt;-&gt;内存镜像加载对照图</h4><p><img src="/img/img/pe_load_table.png" alt="文件镜像&lt;-&gt;内存镜像加载对照图"></p><h3 id="偏移算法"><a href="#偏移算法" class="headerlink" title="偏移算法"></a>偏移算法</h3><p>节的起始地址在磁盘文件中是按照 IMAGE_OPTIONAL_HEADER 结构的 FileAlignment 字段的值进行对齐的，而当被加载到内存中时是按照同一结构中的 SectionAlignment 字段的值对其的，两者的值可能不同，所以一个节被装入内存后相对于文件头的偏移和在磁盘文件中的偏移可能是不同的。</p><p>但是,比如有一个数据在.text节中,.text的在文件镜像中的基址是0x50000 数据的偏移是0x5 那么它在文件镜像中的完整地址就是0x50005 , 在PE文件被载入内存之后 根据对其属性 .txt的基址是0x70000 那么它的偏移是不会发生改变的.那么它在内存镜像中的完整地址就是 0x70005</p><p>所以一个在节表中的数据地址RVA的算法就是 (数据在文件镜像中的真实地址-数据在文件镜像中的所在区段基址)+数据所在区段内存镜像基址</p><h3 id="节表结构-IMAGE-SECTION-HEADER"><a href="#节表结构-IMAGE-SECTION-HEADER" class="headerlink" title="节表结构 IMAGE_SECTION_HEADER"></a>节表结构 IMAGE_SECTION_HEADER</h3><h4 id="节表的开始与结束"><a href="#节表的开始与结束" class="headerlink" title="节表的开始与结束"></a>节表的开始与结束</h4><p>节表在文件镜像中是紧跟在IMAGE_OPTIONAL_HEADER之后</p><p>具体个数不定!</p><p>由PE文件头 IMAGE_NT_HEADERS 结构中的 FileHeader.NumberOfSections 字段来指定的</p><h4 id="节表的结构"><a href="#节表的结构" class="headerlink" title="节表的结构"></a>节表的结构</h4><p>IMAGE_SECTION_HEADER</p><figure class="highlight plaintext"><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">typedef struct _IMAGE_SECTION_HEADER &#123;</span><br><span class="line">  BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];</span><br><span class="line">  union &#123;</span><br><span class="line">    DWORD PhysicalAddress;</span><br><span class="line">    DWORD VirtualSize;</span><br><span class="line">  &#125; Misc;</span><br><span class="line">  DWORD VirtualAddress;</span><br><span class="line">  DWORD SizeOfRawData;</span><br><span class="line">  DWORD PointerToRawData;</span><br><span class="line">  DWORD PointerToRelocations;</span><br><span class="line">  DWORD PointerToLinenumbers;</span><br><span class="line">  WORD  NumberOfRelocations;</span><br><span class="line">  WORD  NumberOfLinenumbers;</span><br><span class="line">  DWORD Characteristics;</span><br><span class="line">&#125; IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;</span><br></pre></td></tr></table></figure><p>name:</p><blockquote><p>多数区块名都习惯性以一个“.”作为开头（例如：.text），这个“.” 实际上是不是必须的。值得我们注意的是，如果区块名达到8 个字节，后面就没有0字符了。并且前边带有一个“$” 的区块名字会从连接器那里得到特殊的待遇，前边带有“$” 的相同名字的区块在载入时候将会被合并，在合并之后的区块中，他们是按照“$” 后边的字符的字母顺序进行合并的。每个区块的名称都是唯一的，不能有同名的两个区块。但事实上节的名称不代表任何含义，他的存在仅仅是为了正规统一编程的时候方便程序员查看方便而设置的一个标记而已。所以将包含代码的区块命名为“.Data” 或者说将包含数据的区块命名为“.Code” 都是合法的。<br>当我们要从PE 文件中读取需要的区块时候，不能以区块的名称作为定位的标准和依据，正确的方法是按照 IMAGE_OPTIONAL_HEADER 结构中的数据目录字段结合进行定位</p></blockquote><p>Misc:</p><blockquote><p>union 基本采用 VirtualSize 对表对应的区块的大小，这是区块的数据在没有进行对齐处理前的实际大小</p></blockquote><p>VirtualAddress:</p><blockquote><p>该区块装载到内存中的RVA 地址。这个地址是按照内存页来对齐的，因此它的数值总是 SectionAlignment 的值的整数倍。</p></blockquote><p>PointerToRawData:</p><blockquote><p>指出节在磁盘文件中所处的位置。这个数值是从文件头开始算起的偏移量 可以认为是这个区块在文件镜像中的基址</p></blockquote><p>SizeOfRawData:</p><blockquote><p>该区块在磁盘中所占的大小，这个数值等于VirtualSize字段的值按照FileAlignment的值对齐以后的大小</p></blockquote><p>Characteristics:</p><blockquote><p>该区块的属性。该字段是按位来指出区块的属性（如代码&#x2F;数据&#x2F;可读&#x2F;可写等）的标志</p></blockquote><h3 id="数据目录"><a href="#数据目录" class="headerlink" title="数据目录"></a>数据目录</h3><h4 id="IMAGE-DATA-DIRCTORY-定义"><a href="#IMAGE-DATA-DIRCTORY-定义" class="headerlink" title="IMAGE_DATA_DIRCTORY 定义"></a>IMAGE_DATA_DIRCTORY 定义</h4><p>IMAGE_DATA_DIRCTORY</p><figure class="highlight plaintext"><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">typedef struct _IMAGE_DATA_DIRECTORY &#123;</span><br><span class="line">DWORD　VirtualAddress; //相对虚拟地址</span><br><span class="line">DWORD　Size;　　　　　 //大小</span><br><span class="line">&#125; IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;</span><br></pre></td></tr></table></figure><h4 id="数据目录做什么用的"><a href="#数据目录做什么用的" class="headerlink" title="数据目录做什么用的?"></a>数据目录做什么用的?</h4><p>顾名思义,数据目录就是PE文件中存储真正数据的地方.固定有16个<br>比如图标&#x2F;菜单&#x2F;图片资源等等…</p><figure class="highlight plaintext"><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">#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 导出表</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 导入表 </span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 资源目录</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 异常目录</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 安全目录</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_BASERELOC         5 重定位基本表</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 调试目录</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 描术字串</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 机器值</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_TLS 9 TLS目录</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 载入配值目录</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT       11 绑定输入表</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_IAT 12 导入地址表</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 延迟载入描述</span><br><span class="line">#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR     14 COM信息</span><br></pre></td></tr></table></figure><p>其实一共有16个但是第十六个保留不适用所以没有写</p>]]>
    </content>
    <id>https://gloomyer.com/2019/03/05/window_pe_02/</id>
    <link href="https://gloomyer.com/2019/03/05/window_pe_02/"/>
    <published>2019-03-05T07:35:32.000Z</published>
    <summary>今天主要讲解什么是节/数据目录/rva 是什么，在执行/加载一个PE文件的时候,系统并不是开始就将整个文件并入内存,而是采用映射关系机制.PE装载器(系统)只建立号虚拟内存地址(RVA)和PE文件的映射关系.而只有当真正执行到某个内存页的指</summary>
    <title>Windows PE 结构学习（二）</title>
    <updated>2019-03-05T07:35:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="安全与逆向" scheme="https://gloomyer.com/categories/%E5%AE%89%E5%85%A8%E4%B8%8E%E9%80%86%E5%90%91/"/>
    <category term="Windows" scheme="https://gloomyer.com/tags/Windows/"/>
    <category term="PE" scheme="https://gloomyer.com/tags/PE/"/>
    <content>
      <![CDATA[<blockquote><p>Windows PE 结构学习</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>PE文件的全称是Portable Executable，意为可移植的可执行的文件，常见的EXE、DLL、OCX、SYS、COM都是PE文件，PE文件是微软Windows操作系统上的程序文件（可能是间接被执行，如DLL）</p><p>简单的说，所有能在Windows上被直接执行或者间接加载执行的就是属于PE文件 比如exe、dll</p><p>PE文件是一个非常复杂的结构</p><p>有图有真相</p><p><img src="/img/img/pe_struct.jpg" alt="完整PE结构图"></p><p>一个操作系统的可执行文件格式在很多方面是这个系统的一面镜子。虽然学习一个可执行文件格式通常不是一个程序员的首要任务，但是你可以从这其中学到大量的知识。</p><p><font size='4' color='red'>阅读需要了解一定c&#x2F;c++语法,了解大小端存储</font></p><h3 id="DOS头"><a href="#DOS头" class="headerlink" title="DOS头"></a>DOS头</h3><p>每一个PE文件都是以DOS头结构开始的，dos头是winDos时代就有了，之所以现在依然有，就是为了兼容dos时代的程序。</p><p>PE头所有的结构体都在winnt.h中</p><p>定义如下</p><figure class="highlight plaintext"><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">typedef struct _IMAGE_DOS_HEADER &#123;      // DOS .EXE header</span><br><span class="line">    WORD   e_magic;                     // Magic number</span><br><span class="line">    WORD   e_cblp;                      // Bytes on last page of file</span><br><span class="line">    WORD   e_cp;                        // Pages in file</span><br><span class="line">    WORD   e_crlc;                      // Relocations</span><br><span class="line">    WORD   e_cparhdr;                   // Size of header in paragraphs</span><br><span class="line">    WORD   e_minalloc;                  // Minimum extra paragraphs needed</span><br><span class="line">    WORD   e_maxalloc;                  // Maximum extra paragraphs needed</span><br><span class="line">    WORD   e_ss;                        // Initial (relative) SS value</span><br><span class="line">    WORD   e_sp;                        // Initial SP value</span><br><span class="line">    WORD   e_csum;                      // Checksum</span><br><span class="line">    WORD   e_ip;                        // Initial IP value</span><br><span class="line">    WORD   e_cs;                        // Initial (relative) CS value</span><br><span class="line">    WORD   e_lfarlc;                    // File address of relocation table</span><br><span class="line">    WORD   e_ovno;                      // Overlay number</span><br><span class="line">    WORD   e_res[4];                    // Reserved words</span><br><span class="line">    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)</span><br><span class="line">    WORD   e_oeminfo;                   // OEM information; e_oemid specific</span><br><span class="line">    WORD   e_res2[10];                  // Reserved words</span><br><span class="line">    LONG   e_lfanew;                    // File address of new exe header</span><br><span class="line">  &#125; IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;</span><br></pre></td></tr></table></figure><p>这里面我们常用的数据有两个</p><ol><li>e_magic </li><li>e_lfanew</li></ol><p>第一个是一个表示，恒定为[5A,4D] ansi为MZ 　一般用于判断是否是DOS_HEADER</p><p>第二个表示NT_HEADER的偏移地址 (计算方法　０+e_lfanew) 0代表基址</p><h3 id="NT-HEADER"><a href="#NT-HEADER" class="headerlink" title="NT_HEADER"></a>NT_HEADER</h3><p>NTHeader 在winnt.h中不存在，　他的结构如下<br>NTHeader的结构如下:</p><figure class="highlight plaintext"><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">struct NT_HEADER &#123;</span><br><span class="line">    DWORD signature; //ansi字符串　恒定为50 45 PE</span><br><span class="line">    struct IMAGE_FILE_HEADER fileHeader; //后续介绍</span><br><span class="line">    struct IMAGE_OPTIONAL_HEADER optionalHeader;//后续介绍</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="IMAGE-FILE-HEADER"><a href="#IMAGE-FILE-HEADER" class="headerlink" title="IMAGE_FILE_HEADER"></a>IMAGE_FILE_HEADER</h3><p>定义</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">typedef struct _IMAGE_FILE_HEADER &#123;</span><br><span class="line">  WORD  Machine;</span><br><span class="line">  WORD  NumberOfSections;</span><br><span class="line">  DWORD TimeDateStamp;</span><br><span class="line">  DWORD PointerToSymbolTable;</span><br><span class="line">  DWORD NumberOfSymbols;</span><br><span class="line">  WORD  SizeOfOptionalHeader;</span><br><span class="line">  WORD  Characteristics;</span><br><span class="line">&#125; IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;</span><br></pre></td></tr></table></figure><p>这里面就有一些我们需要的数据了,我这里只介绍一些基本能用到的，用不到的不介绍了，可以自行查询。</p><p>Machine:</p><blockquote><p>这个PE文件的类型,只会为三个值0x014C(x86架构程序) 0x0200(英特尔安腾架构　已基本废弃) 0x8664(64架构程序)</p></blockquote><p>NumberOfSections:</p><blockquote><p>表示节(块、区段)的数量（节、块、区段　翻译的区别，后续介绍）</p></blockquote><p>TimeDateStamp:</p><blockquote><p>文件编译的时间</p></blockquote><p>SizeOfOptionalHeader:</p><blockquote><p>IMAGE_OPTIONAL_HEADER　的大小(后续介绍)</p></blockquote><p>Characteristics:</p><blockquote><p>文件属性 值含义很多，具体参照<a href="https://docs.microsoft.com/zh-cn/windows/desktop/api/winnt/ns-winnt-_image_file_header">官方文档</a></p></blockquote><h3 id="IMAGE-OPTIONAL-HEADER"><a href="#IMAGE-OPTIONAL-HEADER" class="headerlink" title="IMAGE_OPTIONAL_HEADER"></a>IMAGE_OPTIONAL_HEADER</h3><p>定义:</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">typedef struct _IMAGE_OPTIONAL_HEADER &#123;</span><br><span class="line">    //</span><br><span class="line">    // Standard fields.</span><br><span class="line">    //</span><br><span class="line"></span><br><span class="line">    WORD    Magic;</span><br><span class="line">    BYTE    MajorLinkerVersion;</span><br><span class="line">    BYTE    MinorLinkerVersion;</span><br><span class="line">    DWORD   SizeOfCode;</span><br><span class="line">    DWORD   SizeOfInitializedData;</span><br><span class="line">    DWORD   SizeOfUninitializedData;</span><br><span class="line">    DWORD   AddressOfEntryPoint;</span><br><span class="line">    DWORD   BaseOfCode;</span><br><span class="line">    DWORD   BaseOfData;</span><br><span class="line"></span><br><span class="line">    //</span><br><span class="line">    // NT additional fields.</span><br><span class="line">    //</span><br><span class="line"></span><br><span class="line">    DWORD   ImageBase;</span><br><span class="line">    DWORD   SectionAlignment;</span><br><span class="line">    DWORD   FileAlignment;</span><br><span class="line">    WORD    MajorOperatingSystemVersion;</span><br><span class="line">    WORD    MinorOperatingSystemVersion;</span><br><span class="line">    WORD    MajorImageVersion;</span><br><span class="line">    WORD    MinorImageVersion;</span><br><span class="line">    WORD    MajorSubsystemVersion;</span><br><span class="line">    WORD    MinorSubsystemVersion;</span><br><span class="line">    DWORD   Win32VersionValue;</span><br><span class="line">    DWORD   SizeOfImage;</span><br><span class="line">    DWORD   SizeOfHeaders;</span><br><span class="line">    DWORD   CheckSum;</span><br><span class="line">    WORD    Subsystem;</span><br><span class="line">    WORD    DllCharacteristics;</span><br><span class="line">    DWORD   SizeOfStackReserve;</span><br><span class="line">    DWORD   SizeOfStackCommit;</span><br><span class="line">    DWORD   SizeOfHeapReserve;</span><br><span class="line">    DWORD   SizeOfHeapCommit;</span><br><span class="line">    DWORD   LoaderFlags;</span><br><span class="line">    DWORD   NumberOfRvaAndSizes;</span><br><span class="line">    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];</span><br><span class="line">&#125; IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;</span><br></pre></td></tr></table></figure><p>看着很多，有用的的数据并不多</p><p>AddressOfEntryPoint:</p><blockquote><p>程序执行入口RVA (RVA 相对虚拟地址，后续介绍)</p></blockquote><p>ImageBase:</p><blockquote><p>程序首选装入地址，一般为0x40000 可以在编译的时候修改，但是这个是建议地址，具体看执行的时候操作系统如何分配</p></blockquote><p>SectionAlignment:</p><blockquote><p>内存区块对齐大小</p></blockquote><p>Subsystem:</p><blockquote><p>当前程序是针对什么平台开发的 <a href="https://docs.microsoft.com/zh-cn/windows/desktop/api/winnt/ns-winnt-image_optional_header32">具体取值参考地址</a></p></blockquote><p>FileAlignment:</p><blockquote><p>文件区块对齐大小</p></blockquote><p>NumberOfRvaAndSizes:</p><blockquote><p>数据目录的数组数量，当下基本恒定为16</p></blockquote><p>IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]</p><blockquote><p>数据目录数组　基本恒定16个 具体数据的存放空间</p></blockquote>]]>
    </content>
    <id>https://gloomyer.com/2019/02/28/window_pe_01/</id>
    <link href="https://gloomyer.com/2019/02/28/window_pe_01/"/>
    <published>2019-02-28T07:35:32.000Z</published>
    <summary>PE文件的全称是Portable Executable，意为可移植的可执行的文件，常见的EXE、DLL、OCX、SYS、COM都是PE文件，PE文件是微软Windows操作系统上的程序文件（可能是间接被执行，如DLL）</summary>
    <title>Windows PE 结构学习（一）</title>
    <updated>2019-02-28T07:35:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="安全与逆向" scheme="https://gloomyer.com/categories/%E5%AE%89%E5%85%A8%E4%B8%8E%E9%80%86%E5%90%91/"/>
    <category term="UAF" scheme="https://gloomyer.com/tags/UAF/"/>
    <content>
      <![CDATA[<blockquote><p>UAF漏洞原理</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>当应用程序调用free()释放内存时，如果内存块小于256kb，dlmalloc并不马上将内存块释放回内存，而是将内存块标记为空闲状态。这么做的原因有两个：一是内存块不一定能马上释放会内核（比如内存块不是位于堆顶端），二是供应用程序下次申请内存使用（这是主要原因）。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb，dlmalloc调用mmap()向内核申请一块内存，返回返还给应用程序使用。如果应用程序释放的内存大于256kb，dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块，因为这样的内存块太大了，最好不要长期占用这么大的内存资源。</p><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><h3 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h3><figure class="highlight plaintext"><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">#include &lt;stdio.h&gt;</span><br><span class="line">#include &lt;stdlib.h&gt;</span><br><span class="line">void main(void)&#123;</span><br><span class="line">        int* p1;</span><br><span class="line">        int* p2;</span><br><span class="line">        p1 = malloc(sizeof(int));</span><br><span class="line">        *p1 = 100;</span><br><span class="line">        printf(&quot;p1: \t address:%X \t value=%d\n&quot;, (int)p1, *p1);</span><br><span class="line">        free(p1);//释放内存</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        //接着申请同样大小的内存空间</span><br><span class="line">        p2 = malloc(sizeof(int));</span><br><span class="line">        *p2 = 50;</span><br><span class="line">        printf(&quot;p2: \t address:%X \t value=%d\n&quot;, (int)p2, *p2);</span><br><span class="line">        printf(&quot;p1: \t address:%X \t value=%d\n&quot;, (int)p1, *p1);</span><br><span class="line">        free(p2);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h3><figure class="highlight plaintext"><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">p1:  address:7115260  value=100</span><br><span class="line">p2:  address:7115260  value=50</span><br><span class="line">p1:  address:7115260  value=50</span><br></pre></td></tr></table></figure><p>参考资料:</p><p><a href="https://blog.csdn.net/qq_31481187/article/details/73612451?locationNum=10&fps=1">参考资料(含利用方式)</a></p>]]>
    </content>
    <id>https://gloomyer.com/2019/01/17/uaf-2019-1-17/</id>
    <link href="https://gloomyer.com/2019/01/17/uaf-2019-1-17/"/>
    <published>2019-01-17T07:35:32.000Z</published>
    <summary>当应用程序调用free()释放内存时，如果内存块小于256kb，dlmalloc并不马上将内存块释放回内存，而是将内存块标记为空闲状态。这么做的原因有两个：一是内存块不一定能马上释放会内核（比如内存块不是位于堆顶端），二是供应用程序下次申请</summary>
    <title>UAF 漏洞原理</title>
    <updated>2019-01-17T07:35:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="安全与逆向" scheme="https://gloomyer.com/categories/%E5%AE%89%E5%85%A8%E4%B8%8E%E9%80%86%E5%90%91/"/>
    <category term="Hook" scheme="https://gloomyer.com/tags/Hook/"/>
    <category term="Windows" scheme="https://gloomyer.com/tags/Windows/"/>
    <category term="驱动开发" scheme="https://gloomyer.com/tags/%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91/"/>
    <content>
      <![CDATA[<blockquote><p>Windows 64bit 驱动hook OpenProcess 保护进程</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>Windows64Bit下驱动通过Hook NtOpenProcess实现对进程的保护</p><p>防止其他读写当前进程</p><h2 id="例图"><a href="#例图" class="headerlink" title="例图"></a>例图</h2><p><img src="/img/img/win_64bit_protect_process.gif" alt="GIF图"></p><h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><p>直接看Github <a href="https://github.com/Gloomyer/WinX64ProtectProcess">WinX64保护进程</a></p>]]>
    </content>
    <id>https://gloomyer.com/2018/11/12/win_64bit_protect_process/</id>
    <link href="https://gloomyer.com/2018/11/12/win_64bit_protect_process/"/>
    <published>2018-11-12T13:23:32.000Z</published>
    <summary>Windows64Bit下驱动通过Hook NtOpenProcess实现对进程的保护，防止其他读写当前进程，直接看Github WinX64保护进程</summary>
    <title>Windows 64 位驱动保护进程</title>
    <updated>2018-11-12T13:23:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="Gradle" scheme="https://gloomyer.com/tags/Gradle/"/>
    <category term="打包" scheme="https://gloomyer.com/tags/%E6%89%93%E5%8C%85/"/>
    <content>
      <![CDATA[<blockquote><p>AndroidSutdio3.0利用友盟实现多渠道打包</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>运营统计开始需要统计不同的渠道app用户数量，和安装app来源的占比关系。<br>所以就需要在安装包上做手脚。多渠道打包就是用来解决这个问题的。</p><p>多渠道打包实现其实是比较容易的，方案也不只一个，目前来说应该有两种类型的方式。一种是在打包的时候打不同渠道的包（我们采用这种方式），还有一种是利用apk包做手脚。</p><p>前者慢等于一次要生成N个包，假设一个包生成时间是1分钟，那么10个包就是10分钟。后者快，因为是生成一个包，然后对这个包做手脚。10个包一般也就1分钟+5-6秒的时间。但是前者相对安全。</p><p>我们没有那么多的渠道需求，前者相对来说比较好。</p><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>首先引入友盟统计sdk</p><p>然后按照友盟的文档配置好。</p><p>在AndroidManifest文件种有这么一条。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;meta-data android:name=&quot;UMENG_CHANNEL&quot; android:value=&quot;$&#123;UMENG_CHANNEL_VALUE&#125;&quot; /&gt;</span><br></pre></td></tr></table></figure><p>value 按照这个格式填写，里面的内容自定义 后面需要用。</p><p>打开app&#x2F;build.gradle文件</p><p><font color='red'>在android节点下，defaultConfig节点的versionCode、versionName 后加入</font></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">flavorDimensions &quot;default&quot;</span><br></pre></td></tr></table></figure><p>AndroidStudio3.x 需要</p><h3 id="在android节点下配置签名"><a href="#在android节点下配置签名" class="headerlink" title="在android节点下配置签名"></a>在android节点下配置签名</h3><figure class="highlight plaintext"><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"> signingConfigs &#123;</span><br><span class="line">    release &#123;</span><br><span class="line">        v1SigningEnabled true</span><br><span class="line">        v2SigningEnabled true</span><br><span class="line">        storeFile file(&quot;../keystore/keystore.jks&quot;)</span><br><span class="line">        storePassword &quot;######&quot;</span><br><span class="line">        keyAlias &quot;####&quot;</span><br><span class="line">        keyPassword &quot;######&quot;</span><br><span class="line">    &#125;</span><br><span class="line">    debug &#123;</span><br><span class="line">        storeFile file(&quot;../keystore/keystore.jks&quot;)</span><br><span class="line">        storePassword &quot;######&quot;</span><br><span class="line">        keyAlias &quot;####&quot;</span><br><span class="line">        keyPassword &quot;######&quot;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="在android节点下配置debug、release包配置"><a href="#在android节点下配置debug、release包配置" class="headerlink" title="在android节点下配置debug、release包配置"></a>在android节点下配置debug、release包配置</h3><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">buildTypes &#123;</span><br><span class="line">    debug &#123;</span><br><span class="line">        minifyEnabled false</span><br><span class="line">        zipAlignEnabled true</span><br><span class="line">        jniDebuggable true</span><br><span class="line"></span><br><span class="line">        signingConfig signingConfigs.debug</span><br><span class="line">        proguardFiles getDefaultProguardFile(&#x27;proguard-android.txt&#x27;), &#x27;proguard-rules.pro&#x27;</span><br><span class="line">        buildConfigField &quot;boolean&quot;, &quot;APP_DEBUG&quot;, &quot;true&quot;</span><br><span class="line">    &#125;</span><br><span class="line">    release &#123;</span><br><span class="line">        minifyEnabled false</span><br><span class="line">        zipAlignEnabled true</span><br><span class="line"></span><br><span class="line">        signingConfig signingConfigs.release</span><br><span class="line">        proguardFiles getDefaultProguardFile(&#x27;proguard-android.txt&#x27;), &#x27;proguard-rules.pro&#x27;</span><br><span class="line">        buildConfigField &quot;boolean&quot;, &quot;APP_DEBUG&quot;, &quot;false&quot;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="在android节点下配置渠道"><a href="#在android节点下配置渠道" class="headerlink" title="在android节点下配置渠道"></a>在android节点下配置渠道</h3><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">productFlavors &#123;</span><br><span class="line">    xpro &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    huawei &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    oppo &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    tencent &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    vivo &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    oneplus &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    smartisan &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    baidu &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    qihu360 &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    xiaomi &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    wandoujia &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    meizu &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">productFlavors.all &#123;</span><br><span class="line">    flavor -&gt; flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="在android节点下配置生成的apk文件名-可选"><a href="#在android节点下配置生成的apk文件名-可选" class="headerlink" title="在android节点下配置生成的apk文件名(可选)"></a>在android节点下配置生成的apk文件名(可选)</h3><figure class="highlight plaintext"><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">applicationVariants.all &#123; variant -&gt;</span><br><span class="line">    variant.outputs.all &#123; output -&gt;</span><br><span class="line">        outputFileName = &quot;unreinforced_name:$&#123;defaultConfig.versionName&#125;_code:$&#123;defaultConfig.versionCode&#125;_$&#123;variant.productFlavors[0].name&#125;.apk&quot;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="生成包"><a href="#生成包" class="headerlink" title="生成包"></a>生成包</h3><h4 id="命令方式"><a href="#命令方式" class="headerlink" title="命令方式"></a>命令方式</h4><p>在项目根目录下<br>多渠道打包命令<br>打所有debug<br>.&#x2F;gradlew assembleDebug<br>打所有release<br>.&#x2F;gradlew assembleRelease<br>打单个debug 比如豌豆荚<br>.&#x2F;gradlew assembleWandoujiaDebug<br>打单个release 比如豌豆荚<br>.&#x2F;gradlew assembleWandoujiaRelease</p><p>我是Linux 命令执行规则请按照自己系统</p><h4 id="图形化操作"><a href="#图形化操作" class="headerlink" title="图形化操作"></a>图形化操作</h4><p>在工具栏找到build<br>build-&gt;generate signed bundle&#x2F;APK-&gt;选择APK Next-&gt;配置签名 Next-&gt;选择apk输出目录 -&gt; 选择build type -&gt; 选择要打的渠道（多选）-&gt; 勾选v1&amp;v2 -&gt; 点击finish</p>]]>
    </content>
    <id>https://gloomyer.com/2018/10/16/android_generate_mute_channel_apk/</id>
    <link href="https://gloomyer.com/2018/10/16/android_generate_mute_channel_apk/"/>
    <published>2018-10-16T06:55:59.000Z</published>
    <summary>运营统计开始需要统计不同的渠道app用户数量，和安装app来源的占比关系。所以就需要在安装包上做手脚。多渠道打包就是用来解决这个问题的。多渠道打包实现其实是比较容易的，方案也不只一个，目前来说应该有两种类型的方式。一种是在打包的时候打不同渠</summary>
    <title>Android Studio 3.0 利用友盟实现多渠道打包</title>
    <updated>2018-10-16T06:55:59.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Gloomyer</name>
    </author>
    <category term="Android" scheme="https://gloomyer.com/categories/Android/"/>
    <category term="踩坑" scheme="https://gloomyer.com/tags/%E8%B8%A9%E5%9D%91/"/>
    <category term="推送" scheme="https://gloomyer.com/tags/%E6%8E%A8%E9%80%81/"/>
    <category term="国产 ROM" scheme="https://gloomyer.com/tags/%E5%9B%BD%E4%BA%A7-ROM/"/>
    <content>
      <![CDATA[<blockquote><p>华为PUSH自定义行为Intent的坑</p></blockquote><span id="more"></span><h2 id="概要"><a href="#概要" class="headerlink" title="概要"></a>概要</h2><p>目前，工信部统一推送渠道没音的情况下，APP想要尽可能将内容推送到用户手机上，只有接入多渠道推送来实现。</p><p>其他的姑且不谈，没啥坑，按标准照做即可。</p><p>华为的是个大坑，溅一脸血的那种。</p><h2 id="客户端"><a href="#客户端" class="headerlink" title="客户端"></a>客户端</h2><p>首先，需要你注册一个透明的代理Activity（为了区分其他渠道 华为的我们自己单独处理一层）</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&lt;activity</span><br><span class="line">    android:name=&quot;.model.push.HuaweiPushAct&quot;</span><br><span class="line">    android:theme=&quot;@android:style/Theme.Translucent&quot;&gt;</span><br><span class="line">    &lt;intent-filter&gt;</span><br><span class="line">        &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;</span><br><span class="line">        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;</span><br><span class="line">        &lt;data</span><br><span class="line">            android:host=&quot;com.miaozan.xpro&quot;</span><br><span class="line">            android:path=&quot;/push_detail&quot;</span><br><span class="line">            android:scheme=&quot;hwpush_scheme&quot; /&gt;</span><br><span class="line">    &lt;/intent-filter&gt;</span><br><span class="line">&lt;/activity&gt;</span><br></pre></td></tr></table></figure><p>host:包名</p><p>path:路径</p><p>scheme:渠道 这三个是自定义的 按需配置即可</p><hr><figure class="highlight plaintext"><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">public class HuaweiPushAct extends Activity &#123;</span><br><span class="line">    private static final String TAG = &quot;HuaweiPushAct&quot;;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    protected void onCreate(@Nullable Bundle savedInstanceState) &#123;</span><br><span class="line">        super.onCreate(savedInstanceState);</span><br><span class="line">        requestWindowFeature(Window.FEATURE_NO_TITLE);</span><br><span class="line">        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">@Override</span><br><span class="line">    protected void onResume() &#123;</span><br><span class="line">        super.onResume();</span><br><span class="line">        Intent intent = getIntent();</span><br><span class="line">        Intent startIntent = new Intent(this, V3MainActivity.class);</span><br><span class="line">        try &#123;</span><br><span class="line">            String params = intent.toUri(Intent.URI_INTENT_SCHEME);</span><br><span class="line">            //Loger.E(TAG, &quot;params:&quot; + params);</span><br><span class="line">            params = params.substring(params.indexOf(&quot;push_detail?&quot;) + 12, params.lastIndexOf(&quot;#Intent;&quot;));</span><br><span class="line">            Loger.E(TAG, &quot;params:&quot; + params);</span><br><span class="line">            String[] parames = params.split(&quot;&amp;&quot;);</span><br><span class="line">            for (int i = 0; i &lt; parames.length; i++) &#123;</span><br><span class="line">                String[] temp = parames[i].split(&quot;=&quot;);</span><br><span class="line">                if(temp[0].equals(&quot;type&quot;))&#123;</span><br><span class="line">                    startIntent.putExtra(temp[0], Integer.parseInt(temp[1]));</span><br><span class="line">                &#125;else if (temp[0].equals(&quot;targetUserId&quot;))&#123;</span><br><span class="line">                    startIntent.putExtra(temp[0], Long.parseLong(temp[1]));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        startActivity(startIntent);</span><br><span class="line">        finish();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>就是一个透明的中介Activity</p><p>onResume中解析所需要的参数</p><hr><p>注册完成，生成服务端需要的intent字符串</p><figure class="highlight plaintext"><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">Intent intent = new Intent(Intent.ACTION_VIEW);</span><br><span class="line">intent.setData(Uri.parse(&quot;hwpush_scheme://com.miaozan.xpro/push_detail?type=2&amp;toUserId=1&quot;));</span><br><span class="line">intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);</span><br><span class="line">String intentUri = intent.toUri(Intent.URI_INTENT_SCHEME);</span><br><span class="line">Log.e(&quot;hwpush&quot;, &quot;intnetUri=    &quot; + intentUri);</span><br></pre></td></tr></table></figure><p>hwpush_scheme:manifest文件中注册act的时候的scheme字段后面的</p><p>com.miaozan.xpro:manifest文件中注册act的时候的host字段后面的</p><p>&#x2F;push_detail:manifest文件中注册act的时候的path字段后面的</p><p>type&#x3D;2&amp;toUserId&#x3D;1 自定义行为需要携带的参数，这里有两个多个用&amp;连接即可</p><p>生成的字符串类似如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hwpush_scheme://com.miaozan.xpro/push_detail?type=2&amp;targetUserId=1#Intent;launchFlags=0x10000000;end</span><br></pre></td></tr></table></figure><p>然后就可以把这个字符串交给服务端了</p><h2 id="服务端"><a href="#服务端" class="headerlink" title="服务端"></a>服务端</h2><p>服务端比较简单</p><p>依赖Gson库（json格式化需求）</p><p>直接看代码(不含申请access_token)</p><figure class="highlight plaintext"><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><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line">import com.google.gson.JsonArray;</span><br><span class="line">import com.google.gson.JsonObject;</span><br><span class="line"></span><br><span class="line">import java.io.BufferedReader;</span><br><span class="line">import java.io.IOException;</span><br><span class="line">import java.io.InputStreamReader;</span><br><span class="line">import java.io.OutputStream;</span><br><span class="line">import java.net.HttpURLConnection;</span><br><span class="line">import java.net.URL;</span><br><span class="line">import java.net.URLEncoder;</span><br><span class="line">import java.text.MessageFormat;</span><br><span class="line"></span><br><span class="line">public class Main &#123;</span><br><span class="line"></span><br><span class="line">    private static String appId = &quot;&quot;;//用户在华为开发者联盟申请的appId和appSecret（会员中心-&gt;应用管理，点击应用名称的链接）</span><br><span class="line"></span><br><span class="line">    private static String tokenUrl = &quot;https://login.cloud.huawei.com/oauth2/v2/token&quot;; //获取认证Token的URL</span><br><span class="line"></span><br><span class="line">    private static String apiUrl = &quot;https://api.push.hicloud.com/pushsend.do&quot;; //应用级消息下发API</span><br><span class="line"></span><br><span class="line">    private static String accessToken = &quot;&quot;;//下发通知消息的认证Token  </span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) throws Exception &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        JsonArray deviceTokens = new JsonArray();</span><br><span class="line">        deviceTokens.add(&quot;&quot;); //设备ID，可以为多个</span><br><span class="line"></span><br><span class="line">        JsonObject body = new JsonObject();//仅通知栏消息需要设置标题和内容，透传消息key和value为用户自定义</span><br><span class="line">        body.addProperty(&quot;title&quot;, &quot;Push message title&quot;);//消息标题</span><br><span class="line">        body.addProperty(&quot;content&quot;, &quot;Push message content&quot;);//消息内容体</span><br><span class="line"></span><br><span class="line">        JsonObject param = new JsonObject();</span><br><span class="line"></span><br><span class="line">        param.addProperty(&quot;intent&quot;, &quot;hwpush_scheme://com.miaozan.xpro/push_detail?type=2&amp;targetUserId=1#Intent;launchFlags=0x10000000;end&quot;);</span><br><span class="line">//这里需要将type=2 targetUserId=1 替换成自己所需要的</span><br><span class="line"></span><br><span class="line">        JsonObject action = new JsonObject();</span><br><span class="line">        action.addProperty(&quot;type&quot;, 1);//1:自定义行为</span><br><span class="line">        action.add(&quot;param&quot;, param);//消息点击动作参数</span><br><span class="line"></span><br><span class="line">        JsonObject msg = new JsonObject();</span><br><span class="line">        msg.addProperty(&quot;type&quot;, 3);//3: 通知栏消息，异步透传消息请根据接口文档设置</span><br><span class="line">        msg.add(&quot;body&quot;, body);//通知栏消息body内容</span><br><span class="line">        msg.add(&quot;action&quot;, action);//消息点击动作</span><br><span class="line"></span><br><span class="line">        JsonObject ext = new JsonObject();//扩展信息，含BI消息统计，特定展示风格，消息折叠。</span><br><span class="line">        ext.addProperty(&quot;biTag&quot;, &quot;Trump&quot;);//设置消息标签，如果带了这个标签，会在回执中推送给CP用于检测某种类型消息的到达率和状态</span><br><span class="line"></span><br><span class="line">        JsonObject hps = new JsonObject();//华为PUSH消息总结构体</span><br><span class="line">        hps.add(&quot;msg&quot;, msg);</span><br><span class="line">        hps.add(&quot;ext&quot;, ext);</span><br><span class="line"></span><br><span class="line">        JsonObject payload = new JsonObject();</span><br><span class="line">        payload.add(&quot;hps&quot;, hps);</span><br><span class="line">        System.out.println(payload.toString());</span><br><span class="line">        String postBody = MessageFormat.format(</span><br><span class="line">                &quot;access_token=&#123;0&#125;&amp;nsp_svc=&#123;1&#125;&amp;nsp_ts=&#123;2&#125;&amp;device_token_list=&#123;3&#125;&amp;payload=&#123;4&#125;&quot;,</span><br><span class="line">                URLEncoder.encode(accessToken, &quot;UTF-8&quot;),</span><br><span class="line">                URLEncoder.encode(&quot;openpush.message.api.send&quot;, &quot;UTF-8&quot;),</span><br><span class="line">                URLEncoder.encode(String.valueOf(System.currentTimeMillis() / 1000), &quot;UTF-8&quot;),</span><br><span class="line">                URLEncoder.encode(deviceTokens.toString(), &quot;UTF-8&quot;),</span><br><span class="line">                URLEncoder.encode(payload.toString(), &quot;UTF-8&quot;));</span><br><span class="line"></span><br><span class="line">        String postUrl = apiUrl + &quot;?nsp_ctx=&quot; + URLEncoder.encode(&quot;&#123;\&quot;ver\&quot;:\&quot;1\&quot;, \&quot;appId\&quot;:\&quot;&quot; + appId + &quot;\&quot;&#125;&quot;, &quot;UTF-8&quot;);</span><br><span class="line"></span><br><span class="line">//这里用的urlconnection 发送的post请求，可以根据需要自行修改</span><br><span class="line">        System.out.println(doJsonPost(postUrl, postBody));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    private static String doJsonPost(String urlPath, String Json) &#123;</span><br><span class="line">        // HttpClient 6.0被抛弃了</span><br><span class="line">        String result = &quot;&quot;;</span><br><span class="line">        BufferedReader reader = null;</span><br><span class="line">        try &#123;</span><br><span class="line">            URL url = new URL(urlPath);</span><br><span class="line">            HttpURLConnection conn = (HttpURLConnection) url.openConnection();</span><br><span class="line">            conn.setRequestMethod(&quot;POST&quot;);</span><br><span class="line">            conn.setDoOutput(true);</span><br><span class="line">            conn.setDoInput(true);</span><br><span class="line">            conn.setUseCaches(false);</span><br><span class="line">            conn.setRequestProperty(&quot;Connection&quot;, &quot;Keep-Alive&quot;);</span><br><span class="line">            conn.setRequestProperty(&quot;Charset&quot;, &quot;UTF-8&quot;);</span><br><span class="line">            // 设置文件类型:</span><br><span class="line">            conn.setRequestProperty(&quot;Content-Type&quot;, &quot;application/json; charset=UTF-8&quot;);</span><br><span class="line">            // 设置接收类型否则返回415错误</span><br><span class="line">            //conn.setRequestProperty(&quot;accept&quot;,&quot;*/*&quot;)此处为暴力方法设置接受所有类型，以此来防范返回415;</span><br><span class="line">            conn.setRequestProperty(&quot;accept&quot;, &quot;application/json&quot;);</span><br><span class="line">            // 往服务器里面发送数据</span><br><span class="line">            if (Json != null &amp;&amp; !&quot;&quot;.equalsIgnoreCase(Json)) &#123;</span><br><span class="line">                byte[] writebytes = Json.getBytes();</span><br><span class="line">                // 设置文件长度</span><br><span class="line">                conn.setRequestProperty(&quot;Content-Length&quot;, String.valueOf(writebytes.length));</span><br><span class="line">                OutputStream outwritestream = conn.getOutputStream();</span><br><span class="line">                outwritestream.write(Json.getBytes());</span><br><span class="line">                outwritestream.flush();</span><br><span class="line">                outwritestream.close();</span><br><span class="line">                System.out.println(&quot;hlhupload&quot; + &quot;doJsonPost: conn&quot; + conn.getResponseCode());</span><br><span class="line">            &#125;</span><br><span class="line">            if (conn.getResponseCode() == 200) &#123;</span><br><span class="line">                reader = new BufferedReader(</span><br><span class="line">                        new InputStreamReader(conn.getInputStream()));</span><br><span class="line">                result = reader.readLine();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; catch (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; finally &#123;</span><br><span class="line">            if (reader != null) &#123;</span><br><span class="line">                try &#123;</span><br><span class="line">                    reader.close();</span><br><span class="line">                &#125; catch (IOException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://gloomyer.com/2018/09/05/android_hms_intent/</id>
    <link href="https://gloomyer.com/2018/09/05/android_hms_intent/"/>
    <published>2018-09-05T12:34:05.000Z</published>
    <summary>目前，工信部统一推送渠道没音的情况下，APP想要尽可能将内容推送到用户手机上，只有接入多渠道推送来实现。其他的姑且不谈，没啥坑，按标准照做即可。华为的是个大坑，溅一脸血的那种。</summary>
    <title>华为推送自定义行为 Intent 的坑</title>
    <updated>2018-09-05T12:34:05.000Z</updated>
  </entry>
</feed>
