<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>周期的力量</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://ainavigation.org/</id>
  <link href="https://ainavigation.org/" rel="alternate"/>
  <link href="https://ainavigation.org/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 周期的力量</rights>
  <subtitle>弥合信息差，做世界公民。</subtitle>
  <title>信息差</title>
  <updated>2026-04-05T02:25:26.015Z</updated>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="投資/港卡" scheme="https://ainavigation.org/categories/%E6%8A%95%E8%B3%87-%E6%B8%AF%E5%8D%A1/"/>
    <category term="投資" scheme="https://ainavigation.org/tags/%E6%8A%95%E8%B3%87/"/>
    <category term="港卡" scheme="https://ainavigation.org/tags/%E6%B8%AF%E5%8D%A1/"/>
    <content>
      <![CDATA[<h1 id="港卡之旅-最全版"><a href="#港卡之旅-最全版" class="headerlink" title="港卡之旅(最全版)"></a>港卡之旅(最全版)</h1><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>想要投資海外資產,包括香港股票保險,美股美債等,開立海外銀行賬戶是必須的.近幾年隨著海外資產配置需求的旺盛增長,恰好 <code>香港</code>作為離岸金融中心,地理位置又毗鄰內地,實地去香港辦理港卡掀起一股熱潮.<br><br><br>本文就匯總全網信息,提供一份最完善的港卡教程,如果有新的信息也會及時更新.感謝網友們的分享,恕不能一一署名.</p><h2 id="第一章：證照准備"><a href="#第一章：證照准備" class="headerlink" title="第一章：證照准備"></a>第一章：證照准備</h2><p>由於香港方面監管要求,必須人在香港境內才能開立銀行賬戶,因此我們需要抽出時間前往香港,香港相對於內地來說相當於境外,因此需要辦理一下 <code>港澳通行證</code>,同時進行 <code>簽注</code>,才能入境香港.<br><br></p><h3 id="1-港澳通行證"><a href="#1-港澳通行證" class="headerlink" title="1. 港澳通行證"></a>1. 港澳通行證</h3><p>各地港澳通行證辦理條件都不一樣,總體來說條件還是比較寬鬆的,筆者身處上海,就以上海為例介紹港澳通行證辦理的流程.<br><br></p><p><strong>1.1 隨申辦預約</strong></p><p>上海辦理港澳通行證不同類型人辦理情況不一,本地戶籍可以零門檻辦理通行證,並且可以辦理個人簽注.<br><br><br>對於異地戶籍,戶籍是長三角城市,在上海上學,有上海居住證,有連續12個月上海社保,四種條件滿足其一,就照本地戶籍一樣辦理.<br><br><br>如果異地戶籍不滿足上述條件,最新政策是也可以辦理,但只能是團簽,不過不用擔心,團簽從上海出境去香港是沒問題的.<br><br><br>隨申辦是上海市政府推出的政務客戶端,在隨申辦上進行預約,搜索“出入境”—選擇“預約業務”—按要求填寫個人信息,支持週六辦理,選擇出入境管理局地點時, 最好是出入境總部, 地址是上海市浦東新區民生路1500號,這裡辦理最方便.<br><br><br>順帶提一句,隨申辦裡面護照,台灣通行證和港澳通行證是放在一起預約的,有需要的可以一起辦理,異地戶籍滿足連續12個月上海社保條件就行了.不過現場辦理台灣通行證時工作人員會提醒你現在去不了台灣,先辦了以備不時之需吧.<br><br></p><table><tr><img src="/img/posts/投資/港卡/3.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></tr><tr><td><img src="/img/posts/投資/港卡/4.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td><td><img src="/img/posts/投資/港卡/5.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td></tr></table><br><p><strong>1.2 現場辦理</strong></p><p>預約成功後會收到短信提示,屆時攜帶身份證到了 <code>民生路1500號</code>的 <code>出入境管理局總部</code>,一樓使用身份證打印辦理單,然後上二樓,按指示牌去採集人像拍照(一年內在出入境系統內拍過照可以不用再拍).接著回到扶梯口的取號臺人工取號,在二樓座位上等待叫號.<br><br><br>窗口辦理時,提交表格,簽字錄指紋,拿到受理回執,掃碼付款.注意選擇簽注類型時可以選一年往返多次的,方便多次出入境香港.<br><br><br>最後如果選擇郵寄證照,就到一樓中國郵政工作台,掃碼填寫郵寄信息,到付15元,預計10個工作日送到.<br><br></p><div style="text-align: center">    <table >        <tr>            <td><img src="/img/posts/投資/港卡/6.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>            <td><img src="/img/posts/投資/港卡/7.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>            <td><img src="/img/posts/投資/港卡/8.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>        </tr>        <tr>            <td><img src="/img/posts/投資/港卡/9.jpg"    data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>            <td><img src="/img/posts/投資/港卡/10.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>            <td><img src="/img/posts/投資/港卡/11.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>        </tr>    </table></div><br><p><strong>1.3 證件和簽注</strong><br><br><br>通行證正面是個人信息,背面是簽注信息,簽注過期了注意去自助簽註機續簽.<br><br></p><div>    <table >        <tr style="align: center; width: 100%">            <td><img src="/img/posts/投資/港卡/12.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>            <td><img src="/img/posts/投資/港卡/13.jpg"   data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'/></td>        </tr>    </table></div><br><h3 id="2-其他證照"><a href="#2-其他證照" class="headerlink" title="2. 其他證照"></a>2. 其他證照</h3><br>去香港時,個人身份證(在港辦理銀行卡需要使用)和在內地有業務的銀行,比如中國工商銀行,中國銀行,招商銀行,中國建設銀行,匯豐銀行等的儲蓄卡,也記得隨身攜帶.這類證照基本上大家都已經具備了,就不詳述了.<br><h2 id="第二章：出行規劃"><a href="#第二章：出行規劃" class="headerlink" title="第二章：出行規劃"></a>第二章：出行規劃</h2><br>如果你身處大灣區,甚至就是毗鄰香港的深圳,那抽個時間直接過去就行了,但對於遠離大灣區的人來說,路途遙遠,就需要提前規劃,做好行程安排.<br><h3 id="1-時間安排"><a href="#1-時間安排" class="headerlink" title="1. 時間安排"></a>1. 時間安排</h3><br>建議在]]>
    </content>
    <id>https://ainavigation.org/2026/03/30/%E6%8A%95%E8%B3%87/%E6%B8%AF%E5%8D%A1/%E6%B8%AF%E5%8D%A1%E4%B9%8B%E6%97%85(%E6%9C%80%E5%85%A8%E7%89%88)/</id>
    <link href="https://ainavigation.org/2026/03/30/%E6%8A%95%E8%B3%87/%E6%B8%AF%E5%8D%A1/%E6%B8%AF%E5%8D%A1%E4%B9%8B%E6%97%85(%E6%9C%80%E5%85%A8%E7%89%88)/"/>
    <published>2026-03-30T02:30:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="港卡之旅-最全版"><a href="#港卡之旅-最全版" class="headerlink" title="港卡之旅(最全版)"></a>港卡之旅(最全版)</h1><h2 id="写在前面"><a href="#写在前面" class="headerlink]]>
    </summary>
    <title>港卡之旅(最全版)</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="上網" scheme="https://ainavigation.org/categories/%E4%B8%8A%E7%B6%B2/"/>
    <category term="上網" scheme="https://ainavigation.org/tags/%E4%B8%8A%E7%B6%B2/"/>
    <category term="代理" scheme="https://ainavigation.org/tags/%E4%BB%A3%E7%90%86/"/>
    <category term="訂閱" scheme="https://ainavigation.org/tags/%E8%A8%82%E9%96%B1/"/>
    <content>
      <![CDATA[<h1 id="桌面端和移動端連接海外互聯網的方式"><a href="#桌面端和移動端連接海外互聯網的方式" class="headerlink" title="桌面端和移動端連接海外互聯網的方式"></a>桌面端和移動端連接海外互聯網的方式</h1><h3 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h3><p>想要獲取海外信息,第一步就是訪問海外網站,但是由於衆所周知的原因,中國大陸難以直接訪問相當多的海外網站,這就需要藉助一些技術手段,跨越這個門檻,本文介紹一下筆者使用的一些工具,希望能幫到各位克服這個困難.</p><h3 id="第一章：軟件安裝"><a href="#第一章：軟件安裝" class="headerlink" title="第一章：軟件安裝"></a>第一章：軟件安裝</h3><p><a href="https://github.com/">Github</a> 是出海必知的網站,所有的軟件都通過這個平臺下載安裝.</p><p><a href="https://www.v2ray.com/">v2ray</a> (需科學上網)是訪問海外網站的網絡代理核心,我們可以安裝已內置 <code>v2ray</code>的客戶端軟件</p><p><strong>1. 桌面端軟件</strong></p><p>桌面端 <code>v2ray</code>客戶端軟件有很多,這裏最推薦<a href="https://github.com/2dust/v2rayN/tags">v2rayN</a>, <code>v2rayN</code>支持 <code>Windows</code>, <code>Linux</code>, <code>MacOS</code> 三大平臺, 同時也支持 <code>Clash</code>, <code>sing_box</code> 等其他核心,各位可以自行探索.</p><p><strong>2. 移動端軟件</strong></p><p>移動端 <code>v2ray</code>客戶端軟件分蘋果和安卓兩大平臺,蘋果端推薦<a href="hhttps://apps.apple.com/us/app/v2box-v2ray-client/id6446814690">V2BOX</a>, 安卓端推薦<a href="https://github.com/2dust/v2rayNG/tags">v2rayNG</a>.<br>注意安卓端可以通過 <a href="https://github.com/">Github</a> 下載安裝, 也可以通過 <code>谷歌商店</code>下載安裝, 但是蘋果端只能通過 <code>App Store下載</code>, 並且 <code>App Store</code>必須切換至海外地區,比如港區或者美區.</p><h3 id="第二章：獲取訂閱節點"><a href="#第二章：獲取訂閱節點" class="headerlink" title="第二章：獲取訂閱節點"></a>第二章：獲取訂閱節點</h3><p><code>v2ray</code>客戶端軟件既支持設置單個網絡代理節點, 也支持更爲強大的節點訂閱.節點訂閱會獲取多個代理節點,可測速驗證節點的有效性和網速,支持隨時更新節點.</p><p><strong>1. 免費節點訂閱</strong></p><p><a href="https://github.com/">Github</a>上有不少免費的 <code>v2ray</code>節點訂閱, 剛開始可以試用一下,這裏推薦幾個.</p><ul><li><a href="https://github.com/free-nodes/v2rayfree">v2rayfree</a></li><li><a href="https://github.com/free18/v2ray">free18</a></li><li><a href="https://github.com/shaoyouvip/free">shaoyouvip</a></li></ul><p>免費節點訂閱可以做爲臨時救急使用,更多的免費訂閱各位可以通過 <code>Google</code>搜索關鍵字 <code>v2ray 訂閱 github</code>獲取</p><p><strong>2. 穩定付費訂閱</strong></p><p>免費節點訂閱雖然不花錢,但是極不穩定,節點失效,網絡龜速,時有發生.筆者經歷了一段免費使用經歷後,果斷轉到使用付費訂閱服務,終於不用再擔心 <code>斷網</code>了,這裏推薦一下自己使用的服務,這個平臺性價比比較高,我已經穩定使用了三年,不用擔心跑路的風險.</p><p><a href="https://www.dageyun.net/#/register?code=pssVo7mC">大哥雲</a>(需科學上網,通過免費節點連通), 註冊好之後點擊左側購買訂閱, 推薦購買 <code>年付套餐A 300 GB</code>, 支持支付寶微信, 這個套餐最划算, 可以輸入9折優惠券：<code>mcuE8uOq</code></p><p><img src="/../img/posts/1/dgy1.png" alt="dgy1"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>購買成功後, 點擊左側儀表盤,再點擊一鍵訂閱,可以複製訂閱地址.</p><p><img src="/../img/posts/1/dgy2.png" alt="dgy2"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><h3 id="第三章：v2ray客戶端軟件使用"><a href="#第三章：v2ray客戶端軟件使用" class="headerlink" title="第三章：v2ray客戶端軟件使用"></a>第三章：<code>v2ray</code>客戶端軟件使用</h3><p>這裏就以 <code>Windows</code> 端 <code>v2rayN</code> 爲例演示訂閱地址的使用</p><p><strong>1. 添加節點訂閱</strong><br>先通過<a href="https://github.com/2dust/v2rayN/tags">Github</a>下載安裝好 <code>v2rayN</code></p><p><img src="/../img/posts/1/v2rayN5.png" alt="v2rayN5"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>然後打開軟件按照圖示添加和更新節點訂閱</p><p><img src="/../img/posts/1/v2rayN1.jpeg" alt="v2rayN1"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><img src="/../img/posts/1/v2rayN2.jpeg" alt="v2rayN2"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><img src="/../img/posts/1/v2rayN3.jpeg" alt="v2rayN3"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><img src="/../img/posts/1/v2rayN4.jpeg" alt="v2rayN4"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><strong>2. 監聽端口</strong></p><p>設置菜單裏面的參數設置可以更改默認的本地HTTP(S)監聽端口</p><p><img src="/../img/posts/1/v2rayN6.png" alt="v2rayN6"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><strong>3. 測試排序節點</strong></p><p>在所有節點列表右鍵一鍵測試節點延遲, 測試完畢後選擇最快的節點.</p><p><img src="/../img/posts/1/v2rayN7.jpeg" alt="v2rayN7"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><strong>3. 代理和路由</strong></p><p>設置系統代理, 可以讓整臺電腦網絡環境都能連接外網.在軟件下方配置系統代理,此時軟體的圖標會標稱紅色,至此就可以開始使用了,打開 <a href="https://www.google.com/">Google</a> 試試能不能訪問吧.</p><p>也可以不配置系統代理, 那就需要在瀏覽器中安裝 <code>Proxy SwitchyOmega</code> 插件以監聽代理, 具體教程參考 <code>Proxy SwitchyOmega</code> 安裝指南.</p><p>路由的功能是將入站數據按需求由不同的出站連接發出,以達到按需代理的目的.這一功能的常見用法是分流國內外流量,可以通過內部機製判斷不同地區的流量,然後將它們發送到不同的出站代理,有以下三種路由模式可以選擇.</p><ul><li>繞過大陸(Whitelist)模式:即原先版本裡的白名單,只是白名單內的網站通過節點伺服器代理上網</li><li>黑名單(Blacklist)模式:除了黑名單內的網站,其餘網站都通過節點伺服器代理上網</li><li>全局(Global)模式:所有網站通過節點伺服器代理上網</li></ul><p><img src="/../img/posts/1/v2rayN8.png" alt="v2rayN8"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><strong>3. 開機自啟和更新</strong></p><p>點擊設置後進入參數設置, 選擇v2rayN設置勾選上開機自動啟動.</p><p><img src="/../img/posts/1/v2rayN9.jpeg" alt="v2rayN9"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><img src="/../img/posts/1/v2rayN10.jpeg" alt="v2rayN10"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>點擊檢查更新菜單自動更新軟件</p><p><img src="/../img/posts/1/v2rayN11.jpeg" alt="v2rayN11"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>最後就可以暢通訪問 <a href="https://www.google.com/">Google</a> 了.</p><p><img src="/../img/posts/1/google.png" alt="google"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p>]]>
    </content>
    <id>https://ainavigation.org/2026/03/28/%E6%A1%8C%E9%9D%A2%E7%AB%AF%E5%92%8C%E7%A7%BB%E5%8B%95%E7%AB%AF%E9%80%A3%E6%8E%A5%E6%B5%B7%E5%A4%96%E4%BA%92%E8%81%AF%E7%B6%B2%E7%9A%84%E6%96%B9%E5%BC%8F/</id>
    <link href="https://ainavigation.org/2026/03/28/%E6%A1%8C%E9%9D%A2%E7%AB%AF%E5%92%8C%E7%A7%BB%E5%8B%95%E7%AB%AF%E9%80%A3%E6%8E%A5%E6%B5%B7%E5%A4%96%E4%BA%92%E8%81%AF%E7%B6%B2%E7%9A%84%E6%96%B9%E5%BC%8F/"/>
    <published>2026-03-28T06:15:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="桌面端和移動端連接海外互聯網的方式"><a href="#桌面端和移動端連接海外互聯網的方式" class="headerlink" title="桌面端和移動端連接海外互聯網的方式"></a>桌面端和移動端連接海外互聯網的方式</h1><h3 id="写在前面"]]>
    </summary>
    <title>桌面端和移動端連接海外互聯網的方式</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="IT" scheme="https://ainavigation.org/categories/IT/"/>
    <category term="openwrt" scheme="https://ainavigation.org/tags/openwrt/"/>
    <category term="softetherVPN" scheme="https://ainavigation.org/tags/softetherVPN/"/>
    <category term="VPN" scheme="https://ainavigation.org/tags/VPN/"/>
    <content>
      <![CDATA[<h2 id="一次曲折的-SoftEther-VPN-调试之旅：从无法获取-IP-到路由迷踪"><a href="#一次曲折的-SoftEther-VPN-调试之旅：从无法获取-IP-到路由迷踪" class="headerlink" title="一次曲折的 SoftEther VPN 调试之旅：从无法获取 IP 到路由迷踪"></a>一次曲折的 SoftEther VPN 调试之旅：从无法获取 IP 到路由迷踪</h2><h3 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h3><p>最近在折腾家庭内网穿透，方案选定了在 OpenWrt 软路由上部署 SoftEther VPN Server。目标很简单：出门在外时，能像在家里一样，安全地访问内网的所有设备（<code>192.168.xxx.0/24</code> 网段）。</p><p>我的网络环境不算复杂但也略有特色：PVE 虚拟化环境，其中一台 OpenWrt 作为主路由，网口直通，负责拨号和 DHCP 服务。SoftEther Server 就安装在这台 OpenWrt 上。</p><p>一切似乎都很顺利，直到客户端拨号成功的那一刻——连接状态显示正常，但虚拟网卡就是拿不到期望的内网 IP 地址，只有一个 <code>169.254.x.x</code> 的自动分配地址。这通常意味着一件事：<strong>DHCP 请求失败了</strong>。</p><p>一场横跨多个技术层面的调试就此拉开序幕。</p><h3 id="第一章：DHCP-请求的“黑洞”"><a href="#第一章：DHCP-请求的“黑洞”" class="headerlink" title="第一章：DHCP 请求的“黑洞”"></a>第一章：DHCP 请求的“黑洞”</h3><p>最初的症状非常明确：客户端无法获取 IP。SoftEther 我配置的是“本地桥接”模式，理论上 VPN 客户端应该像一个物理设备一样被接入了我的局域网交换机，DHCP 请求理应能被 OpenWrt 的 DHCP 服务收到。</p><p><strong>我的第一反应是</strong>：是不是 OpenWrt 的防火墙把 DHCP 的广播包给拦了？</p><p>为了验证这一点，我祭出了网络调试的瑞士军刀——<code>tcpdump</code>。通过 SSH 登录到 OpenWrt，我执行了以下命令，专门监听 <code>br-lan</code> 接口上的 DHCP 流量（端口 67 和 68）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 OpenWrt 上执行</span></span><br><span class="line">tcpdump -i br-lan -n port 67 or port 68</span><br></pre></td></tr></table></figure><p>让 VPN 客户端重新连接后，<code>tcpdump</code> 的输出给了我第一个决定性的线索：</p><figure class="highlight plaintext"><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">16:17:38.509738 IP 0.0.0.0.68 &gt; 255.255.255.255.67: BOOTP/DHCP, Request from xx:3b:xx:xx:eb:05, length 300</span><br><span class="line">... (重复多次) ...</span><br></pre></td></tr></table></figure><p><strong>分析</strong>:</p><ul><li><code>br-lan</code> 接口上<strong>确实</strong>看到了来自 VPN 客户端虚拟 MAC 地址（<code>xx:3b:xx:xx:eb:05</code>）的 DHCP 请求广播包。</li><li>但是，输出中<strong>完全没有</strong>看到来自我的 DHCP 服务器（<code>192.168.xxx.254</code>）的任何响应包（<code>Offer</code> 或 <code>ACK</code>）。</li></ul><p>这个发现意义重大。它排除了 SoftEther 本地桥接本身的问题，也排除了 VPN 客户端到 OpenWrt 之间的网络问题。数据包已经成功到达了局域网的“大门口”。问题变成了：<strong>为什么门口的 DHCP 服务员（dnsmasq）不理它？</strong></p><h3 id="第二章：沉默的-DHCP-服务器"><a href="#第二章：沉默的-DHCP-服务器" class="headerlink" title="第二章：沉默的 DHCP 服务器"></a>第二章：沉默的 DHCP 服务器</h3><p>既然 DHCP 包到了，但服务没响应，那么嫌疑人就转移到了 DHCP 服务器本身——在 OpenWrt 中，它是由 <code>dnsmasq</code> 进程负责的。</p><p>一个服务不响应，排查的第一步永远是：<strong>它到底还活着吗？</strong></p><p>我通过 <code>logread</code> 命令来查看 <code>dnsmasq</code> 的系统日志：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">logread | grep dnsmasq</span><br></pre></td></tr></table></figure><p>结果令人震惊，也让整个调试豁然开朗：</p><figure class="highlight plaintext"><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">daemon.crit dnsmasq[1]: cannot read /tmp/dnsmasq.d/dnsmasq-ssrplus.d/gfw_list.conf...</span><br><span class="line">daemon.crit dnsmasq[1]: FAILED to start up</span><br></pre></td></tr></table></figure><p><strong>真相??</strong>：我的 DHCP 服务器根本就没在运行！它在启动时，因为一个名为 <code>ssrplus</code> 的第三方插件配置出错（试图读取一个不存在的文件），导致整个进程直接崩溃退出了。</p><p>一个“死掉”的服务自然不会响应任何请求。这就是 DHCP 请求石沉大海的根本原因。</p><p>后续的分析发现，这是一个典型的“时序问题”。有时 <code>ssrplus</code> 插件的后台进程会晚于 <code>dnsmasq</code> 的启动，导致 <code>dnsmasq</code> 启动时找不到由 <code>ssrplus</code> 生成的配置文件。而在我手动排查的过程中，<code>ssrplus</code> 可能已经完成了它的工作，当我再次尝试重启 <code>dnsmasq</code> 服务时 (<code>/etc/init.d/dnsmasq restart</code>)，文件已经存在，服务就“奇迹般”地恢复了。</p><p>我一开就是这么以为的，那么现在既然<code>dnsmasq</code>已经在运行了，那么我发起vpn客户端的连接，应该就可以了，结果依旧获取不到ip地址，<code>logread | grep dnsmasq</code>检查log，却没有输出任何日志信息。</p><p>这说明，DHCP请求包在到达 br-lan 接口后，在被内核传递给 dnsmasq 这个应用程序之前，就被系统静默地丢弃了。</p><p>  这是一个非常棘手的情况，通常与Linux内核的网络过滤子系统有关。当SoftEther这种非标准的网络程序直接向一个网桥设备（br-lan）注入数据包时，可能会触发一些内核的底层安全或过滤机制<br>  ，导致数据包无法被上层应用（如dnsmasq）正常接收。</p><p>  <strong>最终解决方案</strong>：改用更稳定标准的TAP桥接模式</p><p>  既然直接桥接到 br-lan 的“高级”模式遇到了这种疑难杂症，我们就改用在Linux系统上更稳定、更标准的桥接方法：TAP设备模式。</p><p>  这种模式的原理是：<br>       1.  让SoftEther创建一个标准的虚拟网卡（TAP设备）。<br>                <img src="https://s2.loli.net/2025/12/15/sdy5jvNTMcJS2R4.png" alt="image-20251215131453006"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'><br>       2.  然后我们用OpenWrt自带的、非常成熟的桥接功能，将这个虚拟网卡加入到br-lan中。</p><p><img src="https://s2.loli.net/2025/12/15/9TBf3w2QKX1nb4z.png" alt="image-20251215131622929"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>  这样一来，所有网络数据都通过标准的内核接口来走，可以完美兼容OpenWrt的防火墙和网络服务。</p><h3 id="第三章：柳暗花明，与新的谜团"><a href="#第三章：柳暗花明，与新的谜团" class="headerlink" title="第三章：柳暗花明，与新的谜团"></a>第三章：柳暗花明，与新的谜团</h3><p>在确保 <code>dnsmasq</code> 正常运行后，我再次尝试连接 VPN。这一次，客户端虚拟网卡上终于跳出了熟悉的内网 IP 地址——问题解决！</p><p>但喜悦只持续了半分钟。我发现，虽然能访问内网了，但这台连接了 VPN 的 PC <strong>自己的互联网访问却断了</strong>。</p><p>这又是一个经典问题：<strong>VPN 的路由冲突</strong>。</p><p>默认情况下，VPN 客户端会非常“霸道”地接管系统的默认网关。也就是说，这台 PC 所有的流量（无论访问内网还是外网）都会被一股脑地塞进 VPN 隧道，发往我家的 OpenWrt。如果 OpenWrt 没有被正确配置为替这些 VPN 客户端做 NAT 转发，那么它们自然就无法访问互联网了。</p><p>我的需求是<strong>分离隧道 (Split Tunneling)</strong>：只有访问内网的流量走 VPN，访问互联网的流量还走本地网络。</p><h3 id="第四章：寻找丢失的“默认网关”选项"><a href="#第四章：寻找丢失的“默认网关”选项" class="headerlink" title="第四章：寻找丢失的“默认网关”选项"></a>第四章：寻找丢失的“默认网关”选项</h3><p>根据经验，实现分离隧道最简单的方法是在 Windows 的网络适配器设置里，取消勾选“<strong>在远程网络上使用默认网关</strong>”。</p><p>但奇怪的是，当我找到 SoftEther 创建的虚拟网卡，进入 “TCP&#x2F;IP 属性” -&gt; “高级”，在“IP 设置”选项卡里，这个熟悉的选项竟然<strong>消失了</strong>。</p><p>这说明 SoftEther 的虚拟网卡驱动与标准的 Windows VPN 适配器行为不同，它把路由控制的权力收回到了 SoftEther Client 软件本身。</p><p>于是，我回到 <strong>SoftEther VPN Client Manager</strong>，右键点击我的 <code>home vpn</code> 连接，进入“属性”，再点击“高级设置”，终于在一个角落里找到了“元凶”：</p><p>在 “高级设置” -&gt; “其他设置” 中，有一个选项叫 “<strong>不保留路由表(A)</strong>”。</p><p><img src="https://s2.loli.net/2025/12/15/xqH7plnSdbOGriB.png" alt="image-20251215131746275"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>它的逻辑是反的：</p><ul><li><strong>不勾选（默认）</strong>：客户端会修改路由表，添加默认网关（导致断网）。</li><li><strong>勾选</strong>：客户端<strong>不</strong>修改路由表，只添加访问内网必需的子网路由。</li></ul><p>勾选“不保留路由表”，确定，重连。世界终于清静了。内网访问正常，外网访问也恢复了正常。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>这次看似简单的 VPN 配置问题，最终演变成了一场涉及网络抓包、服务状态检查、第三方插件排错和客户端路由策略设定的全链路调试。整个过程曲折但收获满满，也再次印证了几个朴素的IT运维真理：</p><ol><li><strong>眼见为实</strong>：当怀疑网络问题时，<code>tcpdump</code> 是最诚实的伙伴。它能明确告诉你数据包走到了哪里。</li><li><strong>先看死活</strong>：在调试一个“不响应”的服务前，先用 <code>ps</code> 或 <code>logread</code> 确认它是否真的在运行。一个沉默的服务，很可能是一个已经“死掉”的服务。</li><li><strong>警惕“黑盒”</strong>：第三方插件和自定义脚本在带来便利的同时，也可能以意想不到的方式干扰核心服务。</li><li><strong>理清需求</strong>：VPN 的网络问题，多半是路由问题。想清楚自己需要“完整隧道”还是“分离隧道”，是解决问题的关键。</li></ol><p>从一头雾水到柳暗花明，这种抽丝剥茧、最终定位根源的过程，正是技术调试的魅力所在。</p>]]>
    </content>
    <id>https://ainavigation.org/2025/12/15/%E4%B8%80%E6%AC%A1%E6%9B%B2%E6%8A%98%E7%9A%84SoftEtherVPN%E8%B0%83%E8%AF%95%E4%B9%8B%E6%97%85%EF%BC%9A%E4%BB%8E%E6%97%A0%E6%B3%95%E8%8E%B7%E5%8F%96IP%E5%88%B0%E8%B7%AF%E7%94%B1%E8%BF%B7%E8%B8%AA/</id>
    <link href="https://ainavigation.org/2025/12/15/%E4%B8%80%E6%AC%A1%E6%9B%B2%E6%8A%98%E7%9A%84SoftEtherVPN%E8%B0%83%E8%AF%95%E4%B9%8B%E6%97%85%EF%BC%9A%E4%BB%8E%E6%97%A0%E6%B3%95%E8%8E%B7%E5%8F%96IP%E5%88%B0%E8%B7%AF%E7%94%B1%E8%BF%B7%E8%B8%AA/"/>
    <published>2025-12-15T05:22:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="一次曲折的-SoftEther-VPN-调试之旅：从无法获取-IP-到路由迷踪"><a href="#一次曲折的-SoftEther-VPN-调试之旅：从无法获取-IP-到路由迷踪" class="headerlink" title="一次曲折的 SoftEthe]]>
    </summary>
    <title>一次曲折的 SoftEther VPN 调试之旅：从无法获取 IP 到路由迷踪</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="cybersecurity" scheme="https://ainavigation.org/categories/cybersecurity/"/>
    <category term="kali" scheme="https://ainavigation.org/tags/kali/"/>
    <category term="Strix" scheme="https://ainavigation.org/tags/Strix/"/>
    <category term="security" scheme="https://ainavigation.org/tags/security/"/>
    <content>
      <![CDATA[<h1 id="在-Kali-OS-上安装与配置-Strix-实践笔记"><a href="#在-Kali-OS-上安装与配置-Strix-实践笔记" class="headerlink" title="在 Kali OS 上安装与配置 Strix 实践笔记"></a>在 Kali OS 上安装与配置 Strix 实践笔记</h1><p>Strix 是一款强大的应用安全测试工具，它能够与大型语言模型（LLM）结合，对目标应用进行自动化或半自动化的渗透测试。本笔记旨在记录在 Kali Linux 操作系统上安装和配置 <code>strix-agent</code> 的完整过程，并对每一步操作进行详细的解释，以便于学习和实践。</p><h2 id="准备工作：安装kali，开启-SSH-远程登录"><a href="#准备工作：安装kali，开启-SSH-远程登录" class="headerlink" title="准备工作：安装kali，开启 SSH 远程登录"></a>准备工作：安装kali，开启 SSH 远程登录</h2><p>安装kali就不讲了，基本就是下一步的事，具体根据自己的情况调整即可。</p><p>在进行复杂的安装和配置前，开启 SSH 服务是一个好习惯。这能让你通过远程终端（如 PuTTY、Xshell 或 VSCode Remote）连接到你的 Kali 系统，从而方便地复制粘贴命令、传输文件和进行多任务操作。</p><h3 id="1-修改-SSH-配置文件"><a href="#1-修改-SSH-配置文件" class="headerlink" title="1. 修改 SSH 配置文件"></a>1. 修改 SSH 配置文件</h3><p>首先，我们需要修改 SSH 服务的核心配置文件 <code>sshd_config</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nano /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>打开文件后，你需要找到并修改以下两个关键参数，以允许密码验证登录：</p><ul><li><p><strong><code>#PermitRootLogin prohibit-password</code></strong></p><ul><li><strong>修改为</strong>：<code>PermitRootLogin yes</code></li><li><strong>说明</strong>：此项允许 root 用户通过 SSH 登录。在安全的生产环境中，通常不建议这样做，但在个人实验环境中可以临时开启以方便操作。</li></ul></li><li><p><strong><code>#PasswordAuthentication yes</code></strong></p><ul><li><strong>取消注释</strong>：确保该行前面没有 <code>#</code> 号，并设置为 <code>yes</code>。</li><li><strong>说明</strong>：此项开启了使用用户名和密码进行身份验证的登录方式。</li></ul></li></ul><p>修改完成后，按 <code>Ctrl+X</code>，然后按 <code>Y</code> 和回车键保存并退出 <code>nano</code> 编辑器。</p><h3 id="2-重启并设置-SSH-服务"><a href="#2-重启并设置-SSH-服务" class="headerlink" title="2. 重启并设置 SSH 服务"></a>2. 重启并设置 SSH 服务</h3><p>修改配置后，需要重启 SSH 服务使其生效，并设置为开机自启动。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 重启 SSH 服务</span></span><br><span class="line">sudo systemctl restart ssh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置 SSH 服务开机自启</span></span><br><span class="line">sudo systemctl <span class="built_in">enable</span> ssh</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查 SSH 服务状态</span></span><br><span class="line">sudo systemctl status ssh</span><br></pre></td></tr></table></figure><p>当看到状态显示为 <code>active (running)</code> 时，说明 SSH 服务已成功启动。</p><h2 id="核心依赖：安装-Docker"><a href="#核心依赖：安装-Docker" class="headerlink" title="核心依赖：安装 Docker"></a>核心依赖：安装 Docker</h2><p>根据 Strix 的设计，它可能依赖 Docker 容器来执行某些隔离的测试任务或运行其组件。因此，安装 Docker 是必不可少的一步。</p><p>我们将使用官方推荐的方法，并配置阿里云的镜像源以加速国内的下载速度。</p><h3 id="1-更新软件包列表与安装依赖"><a href="#1-更新软件包列表与安装依赖" class="headerlink" title="1. 更新软件包列表与安装依赖"></a>1. 更新软件包列表与安装依赖</h3><p>首先，更新 <code>apt</code> 包索引，并安装一些允许 <code>apt</code> 通过 HTTPS 使用仓库所必需的软件包。</p><figure class="highlight bash"><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">sudo apt-get update</span><br><span class="line">sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common</span><br></pre></td></tr></table></figure><ul><li><strong>说明</strong>：<ul><li><code>apt-transport-https</code>：让 <code>apt</code> 支持 <code>https://</code> 协议的源。</li><li><code>ca-certificates</code>：允许系统检查安全证书。</li><li><code>curl</code>：用于从网络下载文件。</li><li><code>software-properties-common</code>：提供了 <code>add-apt-repository</code> 命令。</li></ul></li></ul><h3 id="2-添加-Docker-的-GPG-密钥"><a href="#2-添加-Docker-的-GPG-密钥" class="headerlink" title="2. 添加 Docker 的 GPG 密钥"></a>2. 添加 Docker 的 GPG 密钥</h3><p>为了确保我们下载的 Docker 软件包是官方且未经篡改的，需要添加 Docker 官方（此处使用阿里云镜像）的 GPG 密钥。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fSSL https://mirrors.aliyun.com/docker-ce/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/docker.gpg</span><br></pre></td></tr></table></figure><h3 id="3-添加-Docker-的软件源"><a href="#3-添加-Docker-的软件源" class="headerlink" title="3. 添加 Docker 的软件源"></a>3. 添加 Docker 的软件源</h3><p>接下来，将 Docker 的软件仓库地址添加到你的系统中。这样 <code>apt</code> 就能从中找到并安装 Docker。<br><strong>注意</strong>：Kali 是基于 Debian 的，因此我们应使用 Debian 的仓库。<code>bookworm</code> 是 Debian 12 的代号，请根据你的 Kali 版本对应的 Debian 代号进行调整。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/docker.gpg] https://mirrors.aliyun.com/docker-ce/linux/debian bookworm stable&quot;</span> | sudo <span class="built_in">tee</span> /etc/apt/sources.list.d/docker.list &gt; /dev/null</span><br></pre></td></tr></table></figure><h3 id="4-安装-Docker-引擎"><a href="#4-安装-Docker-引擎" class="headerlink" title="4. 安装 Docker 引擎"></a>4. 安装 Docker 引擎</h3><p>万事俱备，现在可以正式安装 Docker 了。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 再次更新软件包列表，以包含刚刚添加的 Docker 仓库</span></span><br><span class="line">sudo apt-get update</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 Docker CE (Community Edition)</span></span><br><span class="line">sudo apt-get install -y docker-ce docker-ce-cli containerd.io</span><br></pre></td></tr></table></figure><h3 id="5-启动并配置-Docker-服务"><a href="#5-启动并配置-Docker-服务" class="headerlink" title="5. 启动并配置 Docker 服务"></a>5. 启动并配置 Docker 服务</h3><p>与 SSH 类似，我们需要启动 Docker 并将其设置为开机自启。</p><figure class="highlight bash"><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">sudo systemctl start docker</span><br><span class="line">sudo systemctl <span class="built_in">enable</span> docker</span><br></pre></td></tr></table></figure><h3 id="6-处理-Docker-用户权限"><a href="#6-处理-Docker-用户权限" class="headerlink" title="6. 处理 Docker 用户权限"></a>6. 处理 Docker 用户权限</h3><p>默认情况下，你需要 <code>sudo</code> 权限才能运行 <code>docker</code> 命令。为了方便，可以将当前用户添加到 <code>docker</code> 用户组中，这样就可以直接执行 <code>docker</code> 命令了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将当前用户（$USER）添加到 docker 组</span></span><br><span class="line">sudo usermod -aG docker <span class="variable">$USER</span></span><br></pre></td></tr></table></figure><ul><li><strong>重要提示</strong>：执行此命令后，你需要 <strong>退出当前终端会话并重新登录</strong>（或者重启系统），这样新的用户组权限才会生效。</li></ul><h2 id="安装-Strix-Agent"><a href="#安装-Strix-Agent" class="headerlink" title="安装 Strix Agent"></a>安装 Strix Agent</h2><p>现在，我们可以安装主角——<code>strix-agent</code> 了。推荐使用 <code>pipx</code> 进行安装，因为它可以将 Python 应用安装在独立隔离的环境中，避免与系统或其他项目的依赖产生冲突。</p><h3 id="1-安装-pipx"><a href="#1-安装-pipx" class="headerlink" title="1. 安装 pipx"></a>1. 安装 pipx</h3><p>如果你的系统中还没有 <code>pipx</code>，可以通过 <code>pip</code> 安装：</p><figure class="highlight bash"><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">python3 -m pip install --user pipx</span><br><span class="line">python3 -m pipx ensurepath</span><br></pre></td></tr></table></figure><h3 id="2-使用-pipx-安装-strix-agent"><a href="#2-使用-pipx-安装-strix-agent" class="headerlink" title="2. 使用 pipx 安装 strix-agent"></a>2. 使用 pipx 安装 strix-agent</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pipx install strix-agent</span><br></pre></td></tr></table></figure><h3 id="3-配置-Strix"><a href="#3-配置-Strix" class="headerlink" title="3. 配置 Strix"></a>3. 配置 Strix</h3><p>Strix 需要知道使用哪个大语言模型（LLM）以及对应的 API 密钥。这通过环境变量来配置。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 设置要使用的 LLM 模型，例如 OpenAI 的 gpt-4</span></span><br><span class="line"><span class="built_in">export</span> STRIX_LLM=<span class="string">&quot;openai/gpt-4&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置你的 API 密钥</span></span><br><span class="line"><span class="built_in">export</span> LLM_API_KEY=<span class="string">&quot;sk-YourActualApiKey&quot;</span></span><br></pre></td></tr></table></figure><ul><li><strong>说明</strong>：<ul><li><code>STRIX_LLM</code>：定义了后端使用的模型。格式通常是 <code>provider/model_name</code>。请根据你拥有的 API 权限填写。</li><li><code>LLM_API_KEY</code>：你的模型提供商的 API 密钥。</li></ul></li><li><strong>建议</strong>：为了不用每次都手动设置，建议将这两行 <code>export</code> 命令添加到你的 shell 配置文件中，例如 <code>~/.bashrc</code> 或 <code>~/.zshrc</code>，然后执行 <code>source ~/.bashrc</code> 使其永久生效。</li></ul><blockquote><p>[!IMPORTANT]</p><h4 id="使用deepseek等国内大模型需要重复设置两个key"><a href="#使用deepseek等国内大模型需要重复设置两个key" class="headerlink" title="使用deepseek等国内大模型需要重复设置两个key"></a>使用deepseek等国内大模型需要重复设置两个key</h4><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">export DEEPSEEK_API_KEY=&quot;sk-YourActualApiKey&quot;</span><br></pre></td></tr></table></figure><p>具体参考<a href="https://docs.litellm.ai/docs/providers%E4%B8%AD">https://docs.litellm.ai/docs/providers中</a> :</p><blockquote><h1 id="API-KEY"><a href="#API-KEY" class="headerlink" title="API KEY"></a>API KEY</h1><figure class="highlight plaintext"><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"># env variable</span><br><span class="line">os.environ[&#x27;DEEPSEEK_API_KEY&#x27;]</span><br></pre></td></tr></table></figure></blockquote></blockquote><h3 id="4-测试运行"><a href="#4-测试运行" class="headerlink" title="4. 测试运行"></a>4. 测试运行</h3><p>一切就绪后，可以执行一个简单的命令来测试 Strix 是否正常工作。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strix --target https://public-firing-range.appspot.com</span><br></pre></td></tr></table></figure><ul><li><strong>说明</strong>：<ul><li><code>--target</code> 参数指定了你要测试的目标 URL。</li><li><code>public-firing-range.appspot.com</code> 是 Google 提供的一个公开的、用于测试 Web 应用扫描器的网站，用它来做初次测试是安全且合适的。</li></ul></li></ul><p>如果一切顺利，你将看到 Strix 开始分析目标并输出测试信息。至此，整个安装和配置过程就完成了。</p>]]>
    </content>
    <id>https://ainavigation.org/2025/12/03/%E5%9C%A8kali%20os%E4%B8%8A%E5%AE%89%E8%A3%85strix/</id>
    <link href="https://ainavigation.org/2025/12/03/%E5%9C%A8kali%20os%E4%B8%8A%E5%AE%89%E8%A3%85strix/"/>
    <published>2025-12-03T06:13:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="在-Kali-OS-上安装与配置-Strix-实践笔记"><a href="#在-Kali-OS-上安装与配置-Strix-实践笔记" class="headerlink" title="在 Kali OS 上安装与配置 Strix 实践笔记"></a>在 Kal]]>
    </summary>
    <title>在 Kali OS 上安装与配置 Strix 实践笔记</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Synology" scheme="https://ainavigation.org/categories/Synology/"/>
    <category term="Synology" scheme="https://ainavigation.org/tags/Synology/"/>
    <category term="Docker" scheme="https://ainavigation.org/tags/Docker/"/>
    <category term="Gitlab" scheme="https://ainavigation.org/tags/Gitlab/"/>
    <content>
      <![CDATA[<h1 id="群晖Docker安装Gitlab"><a href="#群晖Docker安装Gitlab" class="headerlink" title="群晖Docker安装Gitlab"></a>群晖Docker安装Gitlab</h1><p>其实也没有什么特别的想法，就是突然觉得要是自己有个像gitlab或者github的私有服务还蛮酷的，于是，就有了这次的折腾。</p><p>首先声明这篇文章借鉴了<a href="https://www.zhihu.com/people/geescan">涂启标</a>的<a href="https://zhuanlan.zhihu.com/p/109834567">Nas码农篇：群晖Docker安装Gitlab</a>如果对原作者造成了侵权或者困扰，请联系我，我会第一时间删除，并且致以我最真诚的歉意。</p><p>同时由于安装完成后找不到管理员用户的密码，最后在官方doc上找到了，因此这里也把官方doc地址贴上<a href="https://docs.gitlab.com/ee/install/docker.html">GitLab Docker images</a></p><p>那么话不多说，就来说说我自己的折腾经历。</p><h2 id="1-安装gitlab的前提"><a href="#1-安装gitlab的前提" class="headerlink" title="1.安装gitlab的前提"></a>1.安装gitlab的前提</h2><p>先说下我自己的群晖型号DS918+，扩展4G内存，使用两个nvme接口的ssd做了缓存，4块8T的硬盘组了raid10，系统目前已经升级了DSM7.0。</p><p>群晖上安装gitlab有两种方式：</p><ul><li>一种是直接安装套件中心的gitlab。<em><strong>（这种方式我在DSM7.0的套件中心中没有找到）</strong></em></li><li>另一种是在docker中自定义安装。</li></ul><p>因为DSM7.0的套件中心中找不到gitlab，因此我们选择后者据说可以安装最新版本的gitlab。</p><p>注意前提条件：</p><ul><li><p>群晖必须是plus型号，这样才能支持docker。（我的是DS918+，所以没有问题）</p></li><li><p>已经安装Docker套件。（没有安装的就自己在套件中心中搜索docker，安装下就可以了）</p></li><li><p>Gitlab官方推荐内存4G及以上，这里建议8G以上，因为gitlab很容易就会占用掉4g左右的内存。（我安装完成后确实稳定在4.72G左右）</p></li></ul><h2 id="2-安装gitlab"><a href="#2-安装gitlab" class="headerlink" title="2.安装gitlab"></a>2.安装gitlab</h2><p>官方的都是在一个linux完整系统下的docker操作，基本都是命令行，我们这里由于是群晖的DSM系统下的docker，因此大部分都是图形界面，与官方还是有些不同的。但是我这里的方法是21年10月操作的，亲测可以正常安装运行，如果后期有问题请先查询官方doc，同时欢迎留言评论。</p><p>1.在套件中先安装Docker套件。</p><p>2.在docker中，注册页签下，搜索：gitlab，双击下载，选latest</p><p>3.下载完成后，在映像页签下，双击gitlab-ce镜像进行安装。</p><p><em>3.3 【可选】设置环境变量（官方推荐，但是我实际没有操作）</em></p><p><em>Before setting everything else, configure a new environment variable <code>$GITLAB_HOME</code> pointing to the directory where the configuration, logs, and data files will reside. Ensure that the directory exists and appropriate permission have been granted.</em></p><p><em>For Linux users, set the path to <code>/srv/gitlab</code>:</em></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">export GITLAB_HOME=/srv/gitlab</span><br></pre></td></tr></table></figure><p>4.点击高级设置，在弹出的高级选项中，切换到卷页签，按照下面截图，设置目录。</p><p>这里需要添加对应的文件夹到docker目录下，可以使用filestation在docker目录下，创建gitlab目录，然后在gitlab目录下，分别创建logs，config，data来存储日志、配置和数据信息文件。装载路径手动填写。</p><table><thead><tr><th>Local location</th><th>Container location</th><th>Usage</th></tr></thead><tbody><tr><td><code>$GITLAB_HOME/data</code></td><td><code>/var/opt/gitlab</code></td><td>For storing application data.</td></tr><tr><td><code>$GITLAB_HOME/logs</code></td><td><code>/var/log/gitlab</code></td><td>For storing logs.</td></tr><tr><td><code>$GITLAB_HOME/config</code></td><td><code>/etc/gitlab</code></td><td>For storing the GitLab configuration files.</td></tr></tbody></table><p>5.切换端口设置页签，设置一个本地端口，这里指定80容器端口对应本地端口1080。当然也建议将其他本地端口的[自动]改为指定的端口，比如22端口对应的本地端口改为7022之类的，因为后续还要修改配置文件，让克隆地址可以正常显示端口，同时也避免自动获取而带来端口变化而导致的访问问题。</p><p><img src="https://i.loli.net/2021/10/20/pEnGutOlVqZL48m.png" alt="image-20211019170521317"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>这个端口就根据自己的爱好来设置了，注意不要与你已有的服务冲突了就行。比如ssh（22）和web服务（80）。</p><p>6.其他的暂时不用改，直接点击应用，并启动这个docker。正常需要等待一段启动时间，内存飙升到一个比较稳定的数值时，正常就可以访问gitlab的页面了。</p><p><img src="https://i.loli.net/2021/10/20/u5GdVZ3nFBc62eg.png" alt="image-20211019171048060"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>7.浏览器输入nas的ip地址+刚才配置的本地端口号，比如192.168.100.110:8080，这样来访问gitlab，如果此时出现502错误，但是看到了gitlab的图标，这表示服务还没起来，可以再等等。</p><p>8.首次登录，这里我遇到了一个小坑，直接附上官方doc的信息：</p><p>Visit the GitLab URL, and log in with username <code>root</code> and the password from the following command:</p><p>当我们首次访问了我们自己部署的gitlab的服务地址，就会要求我们登录，这时用户名请使用<code>root</code>，密码则使用下面的命令获取：</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">sudo docker exec -it gitlab grep &#x27;Password:&#x27; /etc/gitlab/initial_root_password</span><br></pre></td></tr></table></figure><p>我在尝试使用了这个密码之后发现并不可行，因为我们DSM虽说也是linux系统，但是并不能识别docker，exec命令。<br>因此，我们需要现在群晖中设置开启ssh访问：</p><p><img src="https://i.loli.net/2021/10/20/TUkWFMa5lAh2vVe.png" alt="image-20211019171941845"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>然后再局域网内使用电脑的终端，比如windows的powershell或者cmd都可以通过ssh连接到群晖：</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">ssh 你群晖上的用户名@你自己的群晖的ip -p 你ssh的端口</span><br></pre></td></tr></table></figure><p>如果询问是否建立连接，请输入yes<br>然后输入自己群晖用户名对应的密码（你自己要是不是群晖nas的管理员就别折腾了）<br>通过<code>sudo -i</code>命令获取root权限，再次输入密码<br>然后通过下面的方法获取这个密码文件内容：</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">cat /etc/gitlab/initial_root_password</span><br></pre></td></tr></table></figure><blockquote><p>The password file will be automatically deleted in the first reconfigure run after 24 hours.</p><p>这个初始化的密码会在首次配置启动后的24小时之后删除。</p></blockquote><p>9.设置好root密码后，可以使用root账号登录。一般情况下，这就能正常登录到gitlab后台了。</p><p><img src="https://i.loli.net/2021/10/20/LlqcmxTIAjKn6NX.png" alt="image-20211019173058249"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>看到这里就大功告成了，已经可以去玩耍了。</p>]]>
    </content>
    <id>https://ainavigation.org/2024/06/23/%E7%BE%A4%E6%99%96Docker%E5%AE%89%E8%A3%85Gitlab/</id>
    <link href="https://ainavigation.org/2024/06/23/%E7%BE%A4%E6%99%96Docker%E5%AE%89%E8%A3%85Gitlab/"/>
    <published>2024-06-23T03:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="群晖Docker安装Gitlab"><a href="#群晖Docker安装Gitlab" class="headerlink" title="群晖Docker安装Gitlab"></a>群晖Docker安装Gitlab</h1><p>其实也没有什么特别的想法，就]]>
    </summary>
    <title>群晖Docker安装Gitlab</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="日程管理" scheme="https://ainavigation.org/categories/%E6%97%A5%E7%A8%8B%E7%AE%A1%E7%90%86/"/>
    <category term="GTD" scheme="https://ainavigation.org/tags/GTD/"/>
    <category term="todo" scheme="https://ainavigation.org/tags/todo/"/>
    <category term="日历" scheme="https://ainavigation.org/tags/%E6%97%A5%E5%8E%86/"/>
    <category term="obsidian" scheme="https://ainavigation.org/tags/obsidian/"/>
    <category term="滴答清单" scheme="https://ainavigation.org/tags/%E6%BB%B4%E7%AD%94%E6%B8%85%E5%8D%95/"/>
    <content>
      <![CDATA[<p>由于我还是比较希望可以有一个统一入口或者平台能够完成GTD，而之前已经做了一下应用市场中找到的一些软件，测试后发现还是滴答清单比较好用，那么就想着将滴答清单内嵌到obsidian中，这样每天就只需要打开obsidian，就可以查看到每天做什么等等。如果可以最好能直接在obsidian中添加滴答的代办。</p><p>目前网上看到两种方式：</p><ul><li>使用custom frames插件把web版的滴答直接加载进来，这个应该效果会不错。<a href="https://github.com/Ellpeck/ObsidianCustomFrames">GitHub - Ellpeck&#x2F;ObsidianCustomFrames: An Obsidian plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.</a></li><li>使用obsidian-dida-sync插件把滴答清单同步进obsidian中。<a href="https://github.com/eightHundreds/obsidian-dida-sync">GitHub - eightHundreds&#x2F;obsidian-dida-sync: 滴答清单同步到obsidian(ticktick sync to obsidian)</a><br>那么就都来尝试下看看。</li></ul><p>首先是obsidian-dida-sync插件，按照作者的说明测试了下，这个效果emmmm，怎么说呢，直接看图吧<br><img src="https://s2.loli.net/2024/06/21/HcIDTOzjZ4Uqidh.png" alt="image.png"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>只能说，我还是试下custom frames吧，按照文档测试<br><img src="https://s2.loli.net/2024/06/21/RJTylLosVXONxZb.png" alt="image.png"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'><br>这个效果是真不错，那么可以把之前在obsidian中task相关的费劲的管理都删除了。</p>]]>
    </content>
    <id>https://ainavigation.org/2024/06/21/%E5%9C%A8obsidian%E4%B8%AD%E5%86%85%E5%B5%8C%E6%BB%B4%E7%AD%94%E6%B8%85%E5%8D%95/</id>
    <link href="https://ainavigation.org/2024/06/21/%E5%9C%A8obsidian%E4%B8%AD%E5%86%85%E5%B5%8C%E6%BB%B4%E7%AD%94%E6%B8%85%E5%8D%95/"/>
    <published>2024-06-21T07:37:00.000Z</published>
    <summary>
      <![CDATA[<p>由于我还是比较希望可以有一个统一入口或者平台能够完成GTD，而之前已经做了一下应用市场中找到的一些软件，测试后发现还是滴答清单比较好用，那么就想着将滴答清单内嵌到obsidian中，这样每天就只需要打开obsidian，就可以查看到每天做什么等等。如果可以最好能直接在obs]]>
    </summary>
    <title>在obsidian中内嵌滴答清单</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="日程管理" scheme="https://ainavigation.org/categories/%E6%97%A5%E7%A8%8B%E7%AE%A1%E7%90%86/"/>
    <category term="GTD" scheme="https://ainavigation.org/tags/GTD/"/>
    <category term="日程管理" scheme="https://ainavigation.org/tags/%E6%97%A5%E7%A8%8B%E7%AE%A1%E7%90%86/"/>
    <category term="todo" scheme="https://ainavigation.org/tags/todo/"/>
    <category term="日历" scheme="https://ainavigation.org/tags/%E6%97%A5%E5%8E%86/"/>
    <category term="calendar" scheme="https://ainavigation.org/tags/calendar/"/>
    <content>
      <![CDATA[<p>这篇文章纯粹是我自己觉得虽然obsidian有日历视图，基本可以满足我的一些日程管理，但是没有和日历关联，其次没有办法对我进行提醒。虽然obsidian安装了reminder插件后，手机端开着是有提醒的，但是总觉得不是很舒服，而且操作不够直观，日历视图是有了，但是没办法直接操作，毕竟是通过dataview来做的展示。这部分还是静待obsidian进化吧。</p><p>因此我自己之前一直在使用GTD软件是滴答清单，很好用，但是日历视图功能是付费的，我不认为付费有错，但是它的付费是订阅制，不是买断制的，这我个人觉得难以接受，之前使用过程中也只是偶尔才遇到需要超出免费使用的范围，因此总觉得不划算。</p><p>于是在网上一整收集，网罗了各种，我见到的一些GTD产品，日历APP，做下对比，方便自己选择。</p><p>还是先罗列下自己对GTD软件的期望（但是似乎并不是都能实现，那就看取舍了）：</p><ul><li>日历视图</li><li>可以收费，但尽量选择买断制</li><li>有自己或者调用系统的提醒功能，提醒能够直接操作（如，完成，稍后提醒等）</li><li>对周期性任务可以有跳过</li><li>无截至日期的可以根据设置要求一直提醒，点击完成就结束在那天</li><li>手机端有周视图小组件</li><li>有行程地图，可以做旅游行程</li><li>支持icloud或webdav备份或同步，或者可以同步系统日历</li><li>微信提醒</li><li>导出日历，导出<code>.ical</code>文件</li><li>子任务</li><li>标签</li><li>支持中文</li></ul><table><thead><tr><th></th><th>滴答清单</th><th>notion calender</th><th>google calendar</th><th>Calenders</th><th>时光序</th><th>指尖时光</th><th>一木清单</th><th>土星计划</th><th>ios日历+提醒事项</th><th>时间积木</th><th>我要做计划</th><th>minical</th><th>pendo</th><th>极简代办</th><th>todoist</th><th>motion</th></tr></thead><tbody><tr><td>日历视图</td><td>会员付费，有，还有甘特图</td><td>勉强有，1，2，3天</td><td>拖动，可以点击新建</td><td>有</td><td>有</td><td>有</td><td>有，需要会员</td><td>有</td><td>没有详细的</td><td>有</td><td>有</td><td>有</td><td>有</td><td>无</td><td></td><td></td></tr><tr><td>收费</td><td>168&#x2F;年</td><td>目前免费</td><td>免费</td><td>398终身</td><td>新用户终身118</td><td>98终身</td><td>98终身</td><td>25买断</td><td>iphone用户免费</td><td>183&#x2F;年</td><td>活动时候48&#x2F;年</td><td>88买断</td><td>目前免费</td><td>活动58买断</td><td>318&#x2F;年</td><td>1998&#x2F;年</td></tr><tr><td>提醒</td><td>好用，带耳机还有siri播报</td><td>有，非常弱，且不能操作</td><td>弱，且不能操作</td><td>有</td><td>有，提醒还不错</td><td>有，但pc端似乎用的是系统的提示，只能看下</td><td>有，还不错</td><td>有，但没有进一步操作</td><td>有</td><td>没有提醒？</td><td>非强提醒，类似微博那种，点击只是跳转</td><td>有</td><td>有，弱提醒，没有类似稍后提醒</td><td>没有？</td><td></td><td></td></tr><tr><td>周期性任务</td><td>好用</td><td>好添加不好管理</td><td>方便</td><td>添加方式一般，无跳过等管理</td><td>有，添加一般，无法跳过，只能一天天点</td><td>有，添加方式一般，无跳过等管理</td><td>有，顺延需要输入天数</td><td>有，但不好用</td><td>有，但是无法很好管理</td><td>有</td><td>有，但是没有办法管理</td><td>有，但不好管理</td><td>没有</td><td>没有</td><td></td><td></td></tr><tr><td>代办</td><td>有</td><td>有</td><td>和活动</td><td>有，但不显示在日历中</td><td>分为日程和清单，清单不在日历视图显示</td><td>有区分，可转换很不错，可自动每天提醒</td><td>有</td><td>没有代办，只有日程，有个打卡模式</td><td>没有区分，就是日程，结合提醒事项一起用</td><td>有</td><td>有，因为没有提醒，就。。。</td><td>似乎不区分</td><td>区分</td><td>不区分，有提醒</td><td></td><td></td></tr><tr><td>周小组件</td><td>有</td><td>没有</td><td>无</td><td>没有</td><td>没有</td><td>有</td><td>没有</td><td>没有小组件</td><td>没有</td><td>有</td><td>有</td><td>有，会员</td><td>虽然没有，但是代办很直观</td><td>没有</td><td></td><td></td></tr><tr><td>行程地图</td><td>位置提醒，到达或离开提醒</td><td>没有</td><td>添加位置</td><td>有，可以插入位置</td><td>只可以添加文本位置</td><td>没有</td><td>有，是他的亮点功能</td><td>有个目标地图在内测中</td><td>可以添加位置</td><td>可以添加位置</td><td>没有</td><td>可以添加位置</td><td>日记可以添加位置</td><td>可以添加位置</td><td></td><td></td></tr><tr><td>同步</td><td>自有</td><td>使用google账户同步</td><td>使用google账户同步</td><td>自有，可写入系统日历，可接入多方应用</td><td>自有</td><td>自有</td><td>自有，可icloud和webdav</td><td>自有</td><td>自带，icloud同步</td><td>自带</td><td>自带</td><td>自带，同步到系统日历</td><td>自有，可以icloud同步系统日历</td><td>自有，可以icloud</td><td></td><td></td></tr><tr><td>微信提醒</td><td>有，会员</td><td>无</td><td>无</td><td>无</td><td>有</td><td>有</td><td>有，会员</td><td>没有</td><td>无</td><td>无</td><td>有</td><td>无</td><td>无</td><td>无</td><td></td><td></td></tr><tr><td>导出</td><td>无，图片，文本形式分享</td><td>无</td><td>无</td><td>无，只能导出pdf</td><td>无，文字，图片和表格</td><td>会员可导出excel</td><td>不可，自有格式</td><td>没有</td><td>日历好像可通过icloud中的<code>.ical</code>文件导出</td><td>无，未找到导出设置</td><td>没有，只有分享</td><td>无，以文字光想</td><td></td><td>导出是个压缩包？里面是csv文件</td><td></td><td></td></tr><tr><td>标签</td><td>有，会员</td><td>无</td><td>无</td><td>无</td><td>无</td><td>有</td><td>有，会员</td><td>没有</td><td>没有，或者说不是tag而是列表</td><td>有，但不好用，以颜色区分，甚至是给颜色写tag</td><td>无</td><td>无</td><td>有，名字对应颜色</td><td>无，只是颜色区分</td><td></td><td></td></tr><tr><td>子任务</td><td>有</td><td>无</td><td>无</td><td>无</td><td>有</td><td>有</td><td>有</td><td>没有</td><td>没有</td><td>无</td><td>有</td><td>无</td><td>无</td><td>有</td><td></td><td></td></tr><tr><td>支持中文</td><td>有</td><td>❌，这个年代没有中文，拉胯</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td>支持</td><td></td><td></td></tr><tr><td>亮点功能</td><td>它倒是很平均，但是每项都挺好用的，主要是手机和pc都可以方便的添加代办，手机和pc也都有提醒，提醒后还能直接操作，是稍后提醒，还是忽略，跳过周期，在日和周视图中可以拖动，支持微信转发创建，支持siri，有每日提醒和持续提醒</td><td>可直接拖动日程，每日是按照时间做的时间轴，好看但是没有多实用，毕竟谁会不停的去新建对应时间还要去调整，很多时候就是一个闪念，记下要做什么，已经很好了。</td><td>api是似乎开放的，在日视图下可以拖动任务</td><td>创建事项可以和系统日历联动，可以从其他软件分享创建事项，可以联动zoom，可以用siri</td><td>创建事项等有一套自己的逻辑，整体还算简洁，有记帐等多个其他功能</td><td>指尖和时光序，挺类似的，指尖更注重提醒，整体要更简洁些，价格实惠</td><td>任务地图是亮点，很像滴答，界面简洁清爽，价格实惠</td><td>功能多？名字挺不错的</td><td>日视图可以拖动事件来改变时间？</td><td>可以显示区间，可以导入通讯录，生成生日提醒</td><td>它的界面都是鸭鸭，还算蛮可爱的，有灵感补给，是一些推送，有装扮，分类和计划功能很明确</td><td>最接近ios日历和提醒事项的，可以写入系统，界面简洁统一</td><td>基于markdown，只有日记，笔记，代办，日程这几个功能（但可转换），潦草小猫元素还挺有意思的，内置ai，可以chatGPT（图标是瓦力机器人的眼睛，设计者用心了）。整体体验真的是挺好的，还免费，我会考虑留用</td><td>手势操作，ios日历和提醒事项可以直接同步，可以faceid锁应用，有siri捷径，能共协作，有统计</td><td></td><td></td></tr><tr><td>缺点</td><td>个人觉得稍贵</td><td>需要google，链接不稳定</td><td>google在中国，懂得懂</td><td>本来以为他这个付费的从其他软件导入日历的功能会很好，结果不能识别出列表，也不能显示代办</td><td>有一种多而不精的感觉，日程与系统相同，不顺手</td><td>功能上有点细节缺失，日程与系统相同使用起来没有滴答顺手</td><td>功能上有点细节缺失，不如滴答方便好用</td><td>界面有点不舒服，像是网页嵌套做出来的（个人不喜欢），可能是个人或者小工作室做的</td><td>操作不顺手，取消操作还要二次确认</td><td>操作不顺手，重要功能缺失❌</td><td>侧重计划规划，不是代办，适合做中长期规划，不适合我</td><td>虽然比ios系统自带的功能多，但是吧虽然多了但是也不是很好管理</td><td>没有我个人已经习惯的操作方式，日程就是系统日历。没有周期管理，提醒及操作稍弱。</td><td>没有日历视图，只有列表，极简是了，但是也不方便管理啊，还没有提醒❌</td><td></td><td></td></tr><tr><td>预览</td><td><img src="https://s2.loli.net/2024/06/20/pRSOXgDnPfqb3Yv.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/tXQu1UvefVhZICo.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/8RNOHPx2zbgpCAI.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/vSrZRo8K9IQuXHk.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/W21IJxcYOlESkem.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/qCVW76o9ilgE1vR.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/HiQtyYbNPfj34wL.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/Eg1vfolthn2HJ3V.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/fFL4awA5JyCEUYb.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/20/fZEBxhumAipKM86.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/21/kw1UHi5YZ4JK2Vn.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/21/S4szQOXtxglPaGE.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/21/8DQsjto2LakBxwI.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td><img src="https://s2.loli.net/2024/06/21/MmAPTuoSa7Nrg1Q.gif" alt="|160"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></td><td></td><td></td></tr></tbody></table><p>好了，一通测试对比之后，发现还是滴答清单，因为它的缺点只是相对贵，之后的计划：</p><ul><li><input disabled="" type="checkbox"> 尝试将滴答清单内嵌到obsidian中，看看好不好用 ➕ 2024-06-21 ⏳ 2024-06-27  ⏰ 2024-06-27 📅 2024-06-27</li><li><input disabled="" type="checkbox"> 多尝试使用pendo ➕ 2024-06-21 ⏳ 2024-06-27</li></ul>]]>
    </content>
    <id>https://ainavigation.org/2024/06/19/%E5%90%84%E7%A7%8D%E4%B8%AA%E4%BA%BA%E6%97%A5%E7%A8%8B%E7%AE%A1%E7%90%86GTD%E5%AF%B9%E6%AF%94/</id>
    <link href="https://ainavigation.org/2024/06/19/%E5%90%84%E7%A7%8D%E4%B8%AA%E4%BA%BA%E6%97%A5%E7%A8%8B%E7%AE%A1%E7%90%86GTD%E5%AF%B9%E6%AF%94/"/>
    <published>2024-06-19T05:42:00.000Z</published>
    <summary>
      <![CDATA[<p>这篇文章纯粹是我自己觉得虽然obsidian有日历视图，基本可以满足我的一些日程管理，但是没有和日历关联，其次没有办法对我进行提醒。虽然obsidian安装了reminder插件后，手机端开着是有提醒的，但是总觉得不是很舒服，而且操作不够直观，日历视图是有了，但是没办法直接]]>
    </summary>
    <title>各种个人日程管理GTD对比</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Blog" scheme="https://ainavigation.org/categories/Blog/"/>
    <category term="Hexo" scheme="https://ainavigation.org/tags/Hexo/"/>
    <category term="GitHub" scheme="https://ainavigation.org/tags/GitHub/"/>
    <category term="page" scheme="https://ainavigation.org/tags/page/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>其实我早在2020年就已经用Hexo搭建了自己的博客，并且使用github page发布了静态的博客。但是中间因为各种原因，差不多就算停更了。现在就是自己再回头记录下，不是说做任何事任何时候开始都不晚么~</p><p>so，那就来记录下吧。</p><p>首先，我这边的目的是记录下Hexo建立博客的过程，之前我都是在windows下弄的，现在想想还是用个虚拟机，系统就Kali 23.2了,主要后面也要常用的。Hexo也更新了好多代了。还记得当时18，19年的时候Hexo1升级到Hexo2，好多组件不兼容，改js改到半夜，也是头秃。不知道现在的Hexo对老版本是否支持，正好也再来把之前做的Hexo主图更新下。而这篇博客就记录下这个过程。</p><p>那就让我们开始把。</p><h2 id="安装Hexo"><a href="#安装Hexo" class="headerlink" title="安装Hexo"></a>安装Hexo</h2><h3 id="什么是-Hexo？"><a href="#什么是-Hexo？" class="headerlink" title="什么是 Hexo？"></a>什么是 Hexo？</h3><p>Hexo 是一个快速、简洁且高效的博客框架。<a href="https://hexo.io/zh-cn/">Hexo</a> 使用 <a href="http://daringfireball.net/projects/markdown/">Markdown</a>（或其他标记语言）解析文章，在几秒内，即可利用靓丽的主题生成静态网页。</p><p>安装的这部分我也是参考<a href="https://hexo.io/zh-cn/docs/">官方Doc</a>, 不愿意看我废话的，可以自行参考官方Doc。</p><h3 id="系统环境与Hexo依赖软件-删除kali部分，后续还是windows系统更新博客"><a href="#系统环境与Hexo依赖软件-删除kali部分，后续还是windows系统更新博客" class="headerlink" title="系统环境与Hexo依赖软件(删除kali部分，后续还是windows系统更新博客)"></a>系统环境与Hexo依赖软件(删除kali部分，后续还是windows系统更新博客)</h3><p><del>由于我是虚拟机部署环境，想着是弄完这一次之后，后面要是长久不用，我也能直接用虚拟机恢复出写博客的环境。（反正想法总是一阵一阵的，就先这样吧）。</del></p><ul><li><input checked="" disabled="" type="checkbox"> <del>Kali Linux 2023.2（有需要的改下国内源）</del></li><li><input disabled="" type="checkbox"> <del><a href="http://nodejs.org/">Node.js</a> (Node.js 版本需不低于 10.13，建议使用 Node.js 12.0 及以上版本)</del></li><li><input checked="" disabled="" type="checkbox"> <del><a href="http://git-scm.com/">Git</a></del></li></ul><p><del>这里就不讲虚拟机怎么装Kali系统了，网上太多教程了。</del></p><p>这里可以说是非常的蛋疼，我后面其实已经把kali的环境都搭建好了，但是由于GFW的问题，导致无法再Linux下直接通过ssh上传到github，虽然本着有问题解决问题的态度，但是知道是因为代理导致的，那也就没啥好纠结的了，其次是根据Hexo官方现在都已经使用action来构建和部署github page，那原先想着的打包虚拟机整体环境就没有必要了。</p><p>因此最终决定，抛弃kali环境直接在windows环境下搭建博客环境。</p><ul><li><input checked="" disabled="" type="checkbox"> windows 10</li><li><input disabled="" type="checkbox"> Node.js</li><li><input disabled="" type="checkbox"> Git</li></ul><h4 id="安装Node-js"><a href="#安装Node-js" class="headerlink" title="安装Node.js"></a>安装Node.js</h4><p>我们直接到node.js的官网上下载最新版本的安装包（<a href="https://nodejs.org/en/download/current">Download | Node.js (nodejs.org)</a>）</p><p>根据自己的系统选择对应的32或64bit版本，然后双击运行，一路‘下一步’就完成了，中间建议勾选安装node.js依赖。</p><p>安装完成后可以在开始菜单中看到<br><img src="https://s2.loli.net/2023/10/08/MeQwHWL2yRasS9F.jpg" alt="微信截图_20231008133554"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>点击打开Node.js会弹出命令行，确认Node.js是否安装成功，且版本是多少</p><p><img src="https://s2.loli.net/2023/10/08/1UJ2QLdNXwGIjer.jpg" alt="微信截图_20231008135122"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><h4 id="安装git"><a href="#安装git" class="headerlink" title="安装git"></a>安装git</h4><p>到官网下载git程序包<a href="https://git-scm.com/downloads">Git - Downloads (git-scm.com)</a></p><p>同样也是一路‘下一步’来安装，建议添加系统环境变量和安装git bash。</p><h3 id="安装Hexo-1"><a href="#安装Hexo-1" class="headerlink" title="安装Hexo"></a>安装Hexo</h3><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">sudo npm install -g hexo-cli</span><br></pre></td></tr></table></figure><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">hexo version</span><br></pre></td></tr></table></figure><p>可以使用git bash或者powershell，git bash有右键菜单，可以省的切换目录，我个人觉得比较舒服，所以后面就以这个为例了。</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">npm install hexo</span><br></pre></td></tr></table></figure><p>我这边因为之前在kali中进行部署发现hexo版本通过默认命令安装的不是最新的，因此，我就加上了版本号</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">npm install hexo@6.3.0</span><br></pre></td></tr></table></figure><p>2024-06-24</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install hexo@laster</span><br></pre></td></tr></table></figure><hr><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">npm install -g hexo-cli</span><br></pre></td></tr></table></figure><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">hexo version</span><br></pre></td></tr></table></figure><p>如果你是刚装好node.js，git bash中似乎没有办法找到这个hexo的环境变量，使用下面的命令即可</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">npx hexo version</span><br></pre></td></tr></table></figure><p>2024-06-23<br>这次安装发现版本查看不出来，我最后是到<code>C:\Users\&lt;your name&gt;\node_modules\hexo</code>下<code>package.json</code>中确认的</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;name&quot;: &quot;hexo&quot;,</span><br><span class="line">  &quot;version&quot;: &quot;7.2.0&quot;,</span><br><span class="line">  &quot;description&quot;: &quot;A fast, simple &amp; powerful blog framework, powered by Node.js.&quot;,</span><br><span class="line">  &quot;main&quot;: &quot;dist/hexo&quot;,</span><br><span class="line">  &quot;bin&quot;: &#123;</span><br><span class="line">    &quot;hexo&quot;: &quot;./bin/hexo&quot;</span><br><span class="line">  &#125;,</span><br></pre></td></tr></table></figure><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">npm install hexo</span><br></pre></td></tr></table></figure><p>现在可以安装到最新的hexo</p><hr><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>使用hexo的框架创建一个文件夹，用于部署和生成静态网站</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></pre></td><td class="code"><pre><span class="line">hexo init &lt;folder&gt;</span><br><span class="line">cd &lt;folder&gt;</span><br><span class="line">npm install</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── _config.yml</span><br><span class="line">├── package.json</span><br><span class="line">├── scaffolds</span><br><span class="line">├── source</span><br><span class="line">|   ├── _drafts</span><br><span class="line">|   └── _posts</span><br><span class="line">└── themes</span><br></pre></td></tr></table></figure><h3 id="config-yml"><a href="#config-yml" class="headerlink" title="_config.yml"></a>_config.yml</h3><p>网站的 <a href="https://hexo.io/zh-cn/docs/configuration">配置</a> 信息，您可以在此配置大部分的参数。</p><h3 id="package-json"><a href="#package-json" class="headerlink" title="package.json"></a>package.json</h3><p>应用程序的信息。<a href="https://ejs.co/">EJS</a>, <a href="http://learnboost.github.io/stylus/">Stylus</a> 和 <a href="http://daringfireball.net/projects/markdown/">Markdown</a> 渲染引擎 已默认安装，您可以自由移除。</p><h3 id="scaffolds"><a href="#scaffolds" class="headerlink" title="scaffolds"></a>scaffolds</h3><p><a href="https://hexo.io/zh-cn/docs/writing#%E6%A8%A1%E7%89%88%EF%BC%88Scaffold%EF%BC%89">模版</a> 文件夹。当您新建文章时，Hexo 会根据 scaffold 来创建文件。</p><p>Hexo 的模板是指在新建的文章文件中默认填充的内容。例如，如果您修改 <code>scaffold/post.md</code> 中的 Front-matter 内容，那么每次新建一篇文章时都会包含这个修改。</p><h3 id="source"><a href="#source" class="headerlink" title="source"></a>source</h3><p>资源文件夹是存放用户资源的地方。除 <code>_posts</code> 文件夹之外，开头命名为 <code>_</code> (下划线)的文件 &#x2F; 文件夹和隐藏的文件将会被忽略。Markdown 和 HTML 文件会被解析并放到 <code>public</code> 文件夹，而其他文件会被拷贝过去。</p><h3 id="themes"><a href="#themes" class="headerlink" title="themes"></a>themes</h3><p><a href="https://hexo.io/zh-cn/docs/themes">主题</a> 文件夹。Hexo 会根据主题来生成静态页面。</p><h2 id="恢复站点状态使用"><a href="#恢复站点状态使用" class="headerlink" title="恢复站点状态使用"></a>恢复站点状态使用</h2><h3 id="config-yml-1"><a href="#config-yml-1" class="headerlink" title="_config.yml"></a>_config.yml</h3><p>由于我之前已经用过了hexo并且已经建立过站点了，因此现在我可以通过之前备份的主题以及配置文件来恢复，主要是将自己使用的主题放进<code>themes</code>文件夹，并将<code>_config.yml</code>中的参数比对恢复。</p><p><em><strong>这里建议<code>_config.yml</code>中单独恢复把参数对应的值粘贴过来，因为hexo的版本更新，防止出现解析生成静态站的时候出现各种乱七八糟的问题。</strong></em></p><h3 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h3><p>我现在找到了一个新的主题，很喜欢，<a href="https://async-docs.imalun.com/">Hexo-Theme-Async</a>推荐给大家，我就在这个主题基础上添加了一些个性化修改。</p><p><del>后期希望可以进一步为这个主题做出一些贡献：</del><br>2024-06-24<br>贡献不了一点，作者更新速度也好，修改都很快也很好，算了，我这个web外行就不要瞎掺和了。</p><hr><h4 id="安装该主题依赖"><a href="#安装该主题依赖" class="headerlink" title="安装该主题依赖"></a>安装该主题依赖</h4><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">npm install --save hexo-renderer-less hexo-renderer-ejs hexo-wordcount hexo-generator-feed katex hexo-generator-searchdb swup hexo-generator-category hexo-generator-tag hexo-generator-index</span><br></pre></td></tr></table></figure><p>这边我因为几乎把所有功能都开了，因此安装的依赖比较多。<strong>less和ejs是必须的渲染器。</strong></p><h4 id="安装该主题"><a href="#安装该主题" class="headerlink" title="安装该主题"></a>安装该主题</h4><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">npm i hexo-theme-async@latest</span><br></pre></td></tr></table></figure><h4 id="启用主题"><a href="#启用主题" class="headerlink" title="启用主题"></a>启用主题</h4><p>修改 Hexo 站点配置文件 <code>_config.yml</code>。</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将主题设置为 hexo-theme-async </span></span><br><span class="line"><span class="attr">theme:</span> <span class="string">async</span></span><br></pre></td></tr></table></figure><p>建议安装最新的版本，并且如果不是有无法满足的自定义个性化修改的话，建议就直接只使用原版，并使用该安装版，而<strong>不要去自己clone下来放到<code>/themes</code>目录下</strong>。<em><strong>折腾是由成本的，别问我是怎么知道的😂</strong></em></p><p>如果你硬要改或者就是要clone，那么请看[原因](# github page部署后，主站仅显示背景)</p><p>2024-06-24<br>可以说目前作者提供的自定义以及个性化修改已经非常足够了，可以参照作者大大写好的文档<a href="https://hexo-theme-async.imalun.com/guide/config.html">主题配置 | Hexo-Theme-Async</a></p><hr><h3 id="博客文章"><a href="#博客文章" class="headerlink" title="博客文章"></a>博客文章</h3><p>将之前备份的文章放入<code>source/_post</code>目录。</p><p><em>啊~~，文章日期，封面又不匹配了，再改一遍😟</em></p><h2 id="通过建立本地web服务进行预览"><a href="#通过建立本地web服务进行预览" class="headerlink" title="通过建立本地web服务进行预览"></a>通过建立本地web服务进行预览</h2><p>使用hexo服务器进行本地站的预览，Hexo 3.0 把服务器独立成了个别模块，必须先安装 <a href="https://github.com/hexojs/hexo-server">hexo-server</a> 才能使用。</p><h3 id="安装hexo-server"><a href="#安装hexo-server" class="headerlink" title="安装hexo-server"></a>安装hexo-server</h3><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">npm install hexo-server --save</span><br></pre></td></tr></table></figure><p>安装完成后，输入以下命令以启动服务器，网站会在 <code>http://localhost:4000</code> 下启动。在服务器启动期间，Hexo 会监视文件变动并自动更新，无须重启服务器。</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">hexo server</span><br></pre></td></tr></table></figure><h2 id="生成"><a href="#生成" class="headerlink" title="生成"></a><del>生成</del></h2><p>2024-06-24<br>在现在使用github pages来生成我们的博客网站的情况下，已经不需要再使用静态生成了。</p><hr><h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><p>现在的hexo可以使用workflow中的action直接让github自动生成github pages来部署我们的博客，但是这种方式需要把源代码都提交上去，请自行斟酌是否包含<strong>个人敏感信息</strong>。</p><p>我这里就选择使用action来自动化部署，这样我自己博客的内容也完全托管在github上省的以后再找版本了。</p><p>在你自己的特殊储存库（<code>&lt;yourname.github.io&gt;</code>）中建立<code>.github/workflows/pages.yml</code>，内容：</p><figure class="highlight yaml"><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="attr">name:</span> <span class="string">Pages</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span> <span class="comment"># default branch</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">pages:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">permissions:</span></span><br><span class="line">      <span class="attr">contents:</span> <span class="string">write</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v2</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Use</span> <span class="string">Node.js</span> <span class="number">16.</span><span class="string">x</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-node@v2</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">node-version:</span> <span class="string">&quot;16&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Cache</span> <span class="string">NPM</span> <span class="string">dependencies</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/cache@v2</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">node_modules</span></span><br><span class="line">          <span class="attr">key:</span> <span class="string">$&#123;&#123;</span> <span class="string">runner.OS</span> <span class="string">&#125;&#125;-npm-cache</span></span><br><span class="line">          <span class="attr">restore-keys:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            $&#123;&#123; runner.OS &#125;&#125;-npm-cache</span></span><br><span class="line"><span class="string"></span>      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">run</span> <span class="string">build</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">peaceiris/actions-gh-pages@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">github_token:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">publish_dir:</span> <span class="string">./public</span></span><br></pre></td></tr></table></figure><p>这里就需要我们之前记录下来的npm的版本号(我这边是18)，并修改到Hexo官方的page生成的action中（也就是上面的pages.yml）：</p><figure class="highlight diff"><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="deletion">-     - name: Use Node.js 16.x</span></span><br><span class="line"><span class="addition">+     - name: Use Node.js 18.x</span></span><br><span class="line">        uses: actions/setup-node@v2</span><br><span class="line">        with:</span><br><span class="line"><span class="deletion">-         node-version: &quot;16&quot;</span></span><br><span class="line"><span class="addition">+         node-version: &quot;18&quot;</span></span><br></pre></td></tr></table></figure><p>参考官方的doc：<a href="https://docs.github.com/zh/pages/getting-started-with-github-pages/about-github-pages">关于 GitHub Pages - GitHub 文档</a></p><h3 id="老主题hexagon的问题"><a href="#老主题hexagon的问题" class="headerlink" title="老主题hexagon的问题"></a>老主题hexagon的问题</h3><h4 id="首先是hexo版本与node-js不匹配"><a href="#首先是hexo版本与node-js不匹配" class="headerlink" title="首先是hexo版本与node.js不匹配"></a>首先是hexo版本与node.js不匹配</h4><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">npm install hexo@指定版本</span><br></pre></td></tr></table></figure><p>我们可以参照官方网站来做<a href="https://www.npmjs.com/package/hexo/v/6.3.0">hexo - npm (npmjs.com)</a></p><p>这个问题我这边参考了这篇<a href="https://zhuanlan.zhihu.com/p/491881992">hexo博客网站主页空白或404</a>这里面有说如何降级node.js或者升级Hexo。</p><h4 id="hexo官方给的pages的actions里面用的主分支叫main，我这边master"><a href="#hexo官方给的pages的actions里面用的主分支叫main，我这边master" class="headerlink" title="hexo官方给的pages的actions里面用的主分支叫main，我这边master"></a>hexo官方给的pages的actions里面用的主分支叫main，我这边master</h4><p>这个虽然在Hexo的文档中有说，老的仓库是主分支叫master，但是很容易漏掉，总之我们得确认下。</p><figure class="highlight diff"><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">on:</span><br><span class="line">  push:</span><br><span class="line">    branches:</span><br><span class="line"><span class="deletion">-     - main</span></span><br><span class="line"><span class="addition">+     - master</span></span><br></pre></td></tr></table></figure><p>经历了这些后，我的老主题总算是可以显示了，但是发现代码以及markdown的大部分格式都不对，该主题也年久失修的状态了，还是换个主题吧。</p><hr><h3 id="新主题async的部署问题"><a href="#新主题async的部署问题" class="headerlink" title="新主题async的部署问题"></a>新主题async的部署问题</h3><p>2024-06-24<br>如果上面是按照<code>npm i hexo-theme-async@latest</code>方式使用主题的，那大概率是不会遇到这样的问题的。我这边的做法是先用hexo新版本新建一个本地目录，在本地目录中使用新主题，然后复制<code>_config.async.yml</code>过来，使用<code>hexo server</code>检查没有问题后，复制到对应<code>xxx.github.io</code>的仓库下，然后在这个仓库下用<code>hexo server</code>检查渲染等没有问题，然后<code>commit</code> 再<code>push</code>到远端，等待github pages的action动作完成，检查网页渲染。</p><hr><h4 id="github-page部署后，主站仅显示背景"><a href="#github-page部署后，主站仅显示背景" class="headerlink" title="github page部署后，主站仅显示背景"></a>github page部署后，主站仅显示背景</h4><p>主要就这一个问题，我部署后去主站一看，怎么之后背景，以为是index.html没有内容，但是去gh-pages分支一瞅，这不都在么。就非常奇怪，然后跟着主题doc来来回回走了十多遍，都没有修复。然后去讨论区翻帖子，发现没有人有我这个问题，奇怪了还能成个例了？主要我自己<code>hexo server</code>在本地打开时，是好的啊，没有一点问题，不死心的我再去主站上打开开发者模式对比着看，最终发现是有个网页元素是</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;trm-scroll-container&quot;</span> <span class="attr">class</span>=<span class="string">&quot;trm-scroll-container&quot;</span> <span class="attr">style</span>=<span class="string">&quot;opacity: 0&quot;</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里的<code>opacity: 0</code>然后对应的css的样式<code>source/css/_components/base.less</code>中</p><figure class="highlight css"><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"><span class="selector-class">.trm-scroll-container</span> &#123;</span><br><span class="line">    <span class="attribute">transition</span>: opacity .<span class="number">6s</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>意思是打开网页后0.6s时间内在这个容器中的内容从全透明变成不透明，emmmmm，但是我这边没有执行啊，啥情况？一阵懵逼之后直接在网页上修改元素</p><figure class="highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="deletion">- &lt;div id=&quot;trm-scroll-container&quot; class=&quot;trm-scroll-container&quot; style=&quot;opacity: 0&quot;&gt;</span></span><br><span class="line"><span class="addition">+ &lt;div id=&quot;trm-scroll-container&quot; class=&quot;trm-scroll-container&quot; style=&quot;opacity: 1&quot;&gt;</span></span><br></pre></td></tr></table></figure><p>网页立马有显示了，但是显示的不全，说明是这个问题，再次尝试了好几次之后还是不行，然后去作者主题讨论区去提了个问题（丢人😳）</p><p>一边等作者大大回复，一边写这个过程，结果突然注意到作者大大在doc中有说</p><blockquote><ul><li>通过克隆本仓库安装（不推荐）</li></ul><blockquote><p><strong>DANGER</strong><br>不推荐直接使用这种方式安装，会导致 bug 版本定位和后续升级比较麻烦。如果您需要自定义样式和页面模块，可以优先使用 <a href="https://async-docs.imalun.com/guide/config.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%B7%E5%BC%8F-style">自定义样式</a> 和 <a href="https://async-docs.imalun.com/guide/config.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E6%9D%BF-layout">自定义模板</a> 配置来个性话您的博客，如果以上方式无法满足您的需求时，且不在需要升级时可选择通过这种方式安装。</p></blockquote><details class="details custom-block" open="" style="box-sizing: border-box; border-width: 1px; border-style: solid; border-color: var(--vp-custom-block-details-border); border-image: initial; border-radius: 8px; padding: 16px 16px 8px; line-height: 24px; font-size: var(--vp-custom-block-font-size); color: var(--vp-custom-block-details-text); background-color: var(--vp-custom-block-details-bg); margin: 16px 0px;"><summary style="box-sizing: border-box; touch-action: manipulation; margin: 0px 0px 8px; font-weight: 700; cursor: pointer;">v2.0.0 后版本</summary><p style="box-sizing: border-box; margin: 8px 0px; overflow-wrap: break-word; line-height: 24px;">从 v2.0.0 开始不在支持拉取后直接使用。新版本的脚本使用 TypeScript 进行重构，项目中不在提供打包压缩后的脚本。</p><p style="box-sizing: border-box; margin: 8px 0px; overflow-wrap: break-word; line-height: 24px;">如果您只想修改模板，您可以前往<span>&nbsp;</span><a href="https://github.com/MaLuns/hexo-theme-async/releases" target="_blank" rel="noreferrer" style="box-sizing: border-box; touch-action: manipulation; color: var(--vp-c-brand-1); text-decoration: underline; font-weight: 500; text-underline-offset: 2px; transition: color 0.25s ease 0s, opacity 0.25s ease 0s;">Github Releases</a><span>&nbsp;</span>的 Assets 下载打包文件<span>&nbsp;</span><code style="box-sizing: border-box; font-family: var(--vp-font-family-mono); font-size: var(--vp-code-font-size); color: var(--vp-code-color); border-radius: 4px; padding: 3px 6px; background-color: var(--vp-custom-block-details-code-bg); transition: color 0.25s ease 0s, background-color 0.5s ease 0s;">hexo-theme-async</code><span>&nbsp;</span>。</p><p style="box-sizing: border-box; margin: 8px 0px; overflow-wrap: break-word; line-height: 24px;">如果您仍然想要使用该方式，请 clone 项目后，手动执行 yarn &amp;&amp; yarn run lib:build 以构建压缩后的脚本。</p></details></blockquote><p>这不尴尬了，这么一找果然，作者大大在主题目录下添加了<code>.gitignore</code></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></pre></td><td class="code"><pre><span class="line"># dev esbuild</span><br><span class="line">source/js/</span><br><span class="line">source/plugins/</span><br></pre></td></tr></table></figure><p>那么在clone下来的主题中执行</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">yarn &amp;&amp; yarn run lib:build</span><br></pre></td></tr></table></figure><p>把编译生成的js拷贝到自己的仓库对应目录下</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></pre></td><td class="code"><pre><span class="line">IIFE ..\hexo-theme-async\source\js\plugins\local_search.js 2.03 KB</span><br><span class="line">IIFE ..\hexo-theme-async\source\js\main.js                 18.51 KB</span><br><span class="line">IIFE ..\hexo-theme-async\source\js\plugins\typing.js       583.00 B</span><br><span class="line">IIFE ⚡️ Build success in 14ms</span><br><span class="line">Done in 2.05s.</span><br></pre></td></tr></table></figure><p>删除.gitignore文件后，重新上传部署，终于网站正常打开了。👏👏👏</p>]]>
    </content>
    <id>https://ainavigation.org/2024/06/14/%E5%88%A9%E7%94%A8Hexo%E5%8D%9A%E5%AE%A2%E6%A1%86%E6%9E%B6%E7%BB%93%E5%90%88Github%20Page%E5%88%9B%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8D%9A%E5%AE%A2/</id>
    <link href="https://ainavigation.org/2024/06/14/%E5%88%A9%E7%94%A8Hexo%E5%8D%9A%E5%AE%A2%E6%A1%86%E6%9E%B6%E7%BB%93%E5%90%88Github%20Page%E5%88%9B%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E5%8D%9A%E5%AE%A2/"/>
    <published>2024-06-14T09:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>其实我早在2020年就已经用Hexo搭建了自己的博客，并且使用github page发布了静态的博客。但是中间因为各种原因，差不多就算停更了]]>
    </summary>
    <title>利用Hexo博客框架结合Github Page创建自己的博客</title>
    <updated>2024-06-24T09:06:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="nv" scheme="https://ainavigation.org/tags/nv/"/>
    <category term="qcom" scheme="https://ainavigation.org/tags/qcom/"/>
    <content>
      <![CDATA[<h2 id="安装QXDM"><a href="#安装QXDM" class="headerlink" title="安装QXDM"></a>安装QXDM</h2><p>使用高通账号登录<a href="https://createpoint.qti.qualcomm.com/tools/#">https://createpoint.qti.qualcomm.com/tools/#</a>，然后找到QXDM对应自己系统的安装包，或者先按照Qualcomm Package Manager，然后再安装QXDM。</p><p>因为后面查看nv需要，还需要安装Product Configuration Assistant Tool (PCAT)。</p><h2 id="使用QXDM查询指定NV值"><a href="#使用QXDM查询指定NV值" class="headerlink" title="使用QXDM查询指定NV值"></a>使用QXDM查询指定NV值</h2><p>首先手机通过数据线连接电脑</p><p><img src="https://i.loli.net/2020/11/24/Go1EcFA7HQswnhN.png" alt="image-20201105163743538"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>如果没有的话，可以通过ADB命令来设置diag口：</p><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">adb shell setprop sys.usb.config diag,serial_cdev,rmnet,adb</span><br><span class="line">adb shell setprop persist.usb.config diag,serial_cdev,rmnet,adb</span><br></pre></td></tr></table></figure><p>然后打开QSDM，点击连接：</p><p><img src="https://i.loli.net/2020/11/24/jz83uQTWXGhRgCF.png" alt="image-20201105164946696"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>选择刚才看到的diag的com端口：</p><p><img src="https://i.loli.net/2020/11/24/zYRA8gqsEQdZTca.png" alt="image-20201105165022146"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>选中后点击右侧的connect按钮，连接成功后软件后面会有数据显示，关闭这个对话框，软件主窗口上选择“show nv browser”：</p><p><img src="https://i.loli.net/2020/11/24/lmHtJ4F8uNMPD75.png" alt="image-20201105170321383"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>如果已经安装，就会自动打开界面并读取：</p><p><img src="https://i.loli.net/2020/11/24/lIoFrwWiH7OSnhG.png" alt="image-20201105171342367"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>读取完成后就会可以在id中输入需要查询的nvid，来查看：</p><p><img src="https://i.loli.net/2020/11/24/jmLkZNP5zIbtiXh.png" alt="image-20201105171543336"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>选中需要读取的项，点击read，如果失败则如下图：</p><p><img src="https://i.loli.net/2020/11/24/Cu7GMUKjP6gbdXa.png" alt="image-20201105171727278"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'><br>如果成功则会提示success。</p><p>当然如果需要临时修改也可在input中输入你需要修改为的值，然后点击write，同样下面会有对应操作的提示：</p><p><img src="https://i.loli.net/2020/11/24/oOcMxuhUyBV7NYJ.png" alt="image-20201105171910420"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p>]]>
    </content>
    <id>https://ainavigation.org/2024/06/14/%E9%AB%98%E9%80%9A%E6%9F%A5%E6%89%BE%E6%8C%87%E5%AE%9Anv%E5%80%BC/</id>
    <link href="https://ainavigation.org/2024/06/14/%E9%AB%98%E9%80%9A%E6%9F%A5%E6%89%BE%E6%8C%87%E5%AE%9Anv%E5%80%BC/"/>
    <published>2024-06-14T09:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="安装QXDM"><a href="#安装QXDM" class="headerlink" title="安装QXDM"></a>安装QXDM</h2><p>使用高通账号登录<a href="https://createpoint.qti.qualcomm.com/]]>
    </summary>
    <title>Android高通平台查找指定nv值</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Learn" scheme="https://ainavigation.org/categories/Learn/"/>
    <category term="obsidian" scheme="https://ainavigation.org/tags/obsidian/"/>
    <category term="note" scheme="https://ainavigation.org/tags/note/"/>
    <category term="blog" scheme="https://ainavigation.org/tags/blog/"/>
    <content>
      <![CDATA[<h2 id="需求以及笔记软件的比较"><a href="#需求以及笔记软件的比较" class="headerlink" title="需求以及笔记软件的比较"></a>需求以及笔记软件的比较</h2><p>首先markdown语法真的好用，我也是typora的付费用户，typora用来写一些文档确实不错，渲染效果各方面都好，就是不适合作为一个笔记软件，或者说，我其实更希望能有一个all in one的软件来对我自己的日常事物进行管理，类似于第二大脑，我也浅浅的用了下notion，很方便，但是没有类似于滴答清单的提醒功能，而滴答清单的日历视图是收费，所以我就找到了obsidian，其实我到写这篇随笔或者说学习笔记的时候都不确定obsidian是否可以达到我的期望，但是我决定试一下。从网上看到很多obsidian的视频以及微信公众号中很多介绍，至少乍一下，很符合我程序猿的习惯，似乎obsidian很多强大的功能都是通过插件来完成的，那我就一步步开始记录自己的折腾之路。</p><p>那么接下来先让我来整理下notion和obsidian之间的差异，感觉很有可能如果obsidian不能满足我期望的时候，两者公用可能是我最好的解决方案。</p><table><thead><tr><th align="center"></th><th align="center"><font color="#8064a2"><strong>notion</strong></font></th><th align="center"><font color="#8064a2"><strong>obsidian</strong></font></th></tr></thead><tbody><tr><td align="center">想法记录方式</td><td align="center">有序，有逻辑的</td><td align="center">无序的，混沌的</td></tr><tr><td align="center">原生是否有提醒功能</td><td align="center">无</td><td align="center">无</td></tr><tr><td align="center">是否支持markdown</td><td align="center">支持</td><td align="center">支持</td></tr><tr><td align="center">是否支持多端同步</td><td align="center">免费用户也支持同步</td><td align="center">本地文件，同步取决于自己</td></tr><tr><td align="center">是否支持日历视图</td><td align="center">支持</td><td align="center">通过插件实现</td></tr><tr><td align="center">是否支持知识图谱</td><td align="center">不支持</td><td align="center">支持</td></tr><tr><td align="center">发布</td><td align="center">官方自带</td><td align="center">官方付费，可以联动hexo</td></tr></tbody></table><p>暂时想到这些，后续想到再加吧。总的来看，notion更加的完善是一个相对完善完整的产品，而obsidian就好像还在进化，同时因为其开源以及三方插件（管中窥豹）的开放和自由，因此似乎其更能完成复杂需求，也就是潜力高。后面看看我能给它整成啥样吧。</p><p>ok，来整理下我的需求：</p><ul><li>日历视图</li><li>手机端，电脑端多端多地同步</li><li>可以通过系统（pc和ios）提醒代办事项</li><li>有知识链接图谱</li><li>可以联动hexo发布博客</li></ul><h2 id="obsidian学习记录"><a href="#obsidian学习记录" class="headerlink" title="obsidian学习记录"></a>obsidian学习记录</h2><p>先把视频教学看起来，我选择的是PKMer在B站发布的视频，这边就链接他们这个【从零开始学OB】系列的第一个视频，感谢作者的辛苦劳作，如果涉及了版权问题请联系我。</p><iframe src="http://player.bilibili.com/player.html?isOutside=true&aid=574356314&bvid=BV19z4y1s7nk&cid=1223043822&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="800" height="600"></iframe><p>观看视频记录的一些想法：</p><ul><li>tag似乎不错，不过不能有空格</li><li>可以和hexo联动发布博客，看着不错，回头折腾下 <ul><li><input checked="" disabled="" type="checkbox"> 和hexo联动发布博客 📅 2024-06-18 ✅ 2024-06-17</li></ul></li><li>obsidian的链接功能确实强大<code>#</code>对应标题，<code>^</code>对应文本块，<code>|</code>可以指定别名</li><li>日记，时间戳笔记真不错，其实应该可以增加 <ul><li><input checked="" disabled="" type="checkbox"> 时间戳文本块，方便记录 [[#^e6b1fc]] 📅 2024-06-18</li></ul></li></ul><ul><li>白板类似思维导图好功能，可以增加笔记，那就是可以多联，应该有点东西</li></ul><p>视频看到这里，突然觉得上面两个可以作为代办事项，于是用了下[Reminder](<a href="https://pkmer.cn/Pkmer-Docs/10-obsidian/obsidian%E7%A4%BE%E5%8C%BA%E6%8F%92%E4%BB%B6/obsidian-reminder-plugin/">PKMer_Obsidian 插件：Reminder 为待办任务增加提醒</a>)插件，可以方便的添加一个任务提示时间点，很不错，还可以用系统提示，nice~</p><p>2024-06-11<br>OB中可以和hexo一样，通过YAML增加文章属性，并且还渲染的很好，就做了一个博客的模板来方便后面通过hexo发布到自己的博客上，于是安装了picGo，以及安装上自动上传的obsidian插件。<br>同时为了方便记录当前时间下载了Natural Language Dates插件，<del>通过<code>@</code>来快速插入时间。</del> &#x3D;&#x3D;事实证明还是用模板自带的时间更好(我设置了快捷键<code>alt+D</code>插入日期，还不错)&#x3D;&#x3D;，因为自然语言插件插入的时间会自动生成一个双链，虽然不直接创建这个文件，但是关系图谱中有了。<br>下载日历插件显示日期以及周数，可以快速点击生成笔记。 ^e6b1fc</p><p>2024-06-12<br>安装了excalidraw和typewritescoll插件<br>安装了fileExpolercount和various-complements插件</p><p>2024-06-13<br>安装了remotely save插件，想着iphone手机上通过群晖的webdav来实现远程同步，既能保障本地化以及实时备份同步，又能有不输云服务的同步性能。<br>在这一趴中遇到了几个小问题，先是ios11之后就禁止app使用http不安全的api，只能使用https，虽然群晖套件中的webdav可以开https，但是又有个新的问题就是ssl证书的问题，群晖自己的证书不是公有的，并不授信，所以，我先用阿里云申请了一个3个月的免费ssl证书测试了下，测试成功。同步的时候还需要设置成相同的根目录；好吧，总的来说，还是成功了。<br>那么 </p><ul><li><input checked="" disabled="" type="checkbox"> 测试一下reminder是否可以在iphone上调用系统提醒功能 📅 2024-06-14<br>✅ 2024-06-14 需要手机开着obsidian，似乎只能在obsidian中提示没有系统提醒。</li></ul><p>安装了kanban和emoji表情👍<br>安装了auto link title和editing toolbar<br>floating Toc因为官方有了大纲，暂时就不装了<br>安装了callout manager<br>wow~，<a href="https://pkmer.cn/products/widget/widgetMarket/">PKMer_挂件集市</a>这个可以哦🤩</p><p>2024-06-14<br>pkmer的教程看的差不多了，暂时就通过下载插件来完善obsidian功能，首先是日历视图，下载了一堆插件：</p><ul><li>full calendar ❌</li><li>tasks ✔️</li><li>day planner ❌</li><li>task calendar wrap ✔️</li><li>Time Ruler ✔️</li><li>timeline view 🔰</li><li>big calender ❌</li></ul><p>安装了full calendar，拥有了日历视图。但是任务管理系统不够完善，下载tasks和day planner插件尝试关联。<br>full calendar必须建立一个笔记用它规定的元数据也就是属性来做，导致tasks插件以及其他插件是找不到这个任务的。暂时放弃❌<br>tasks可以直接找到带有<code>#task</code>标签的任务或者是markdown中todo列表格式的作为任务，可以在笔记中利用模板也能在看板中添加卡片，通过dataview可以显示出日历视图（借用task calendar wrap中整合的task calendar和task timeline）<br>day planner是基于日记规划的，有周视图但是更侧重这一天的规划，且该插件启动加载有问题❌<br>Time Ruler也可以显示出每天的task，也有每天的时间线<br>timeline view是以点的形式显示，是建立一个exclidraw来显示的，可以缩放<br>big calender也是需要使用daily note 来建立日历视图的，所以暂时pass❌</p><p>测试之后，总结出自己的一套日程管理使用方式：</p><ol><li>主要使用task来建立任务池，将其添加到看板卡片中。看板的卡片并不会生成一个笔记，所以我觉得还行，我并不是很喜欢多出很多笔记来做日程管理。</li><li>使用笔记文章中使用todo格式（好像也能调用快捷键创建task模板来添加）来直接设置代办事项，很适合学习或者做事的时候突然想到要做个什么事情。</li></ol><p>来看下联动hexo发布博客<br><a href="https://forum-zh.obsidian.md/t/topic/32180">基于obsidian插件、hexo和github action的博客方案 - 经验分享 - Obsidian 中文论坛</a><br>这篇文章主要在说本地部署预览然后上传的方式，去年我折腾的时候hexo已经改用action来生成github page了，所以没多大用。<br><a href="https://segmentfault.com/a/1190000042111566">Hexo + Obsidian + Git 完美的博客部署与编辑方案</a><br>这篇文章相对我来说更有价值一些，利用git插件上传github仓库，然后action会自动生成，似乎更合适，那么开始实践。<br>把这篇文章上传到github仓库，并显示到自己的博客上。试试看~<br><em>这个git只是能在obsidian中调用git，并进行一个显示，主体还是需要用git来操作的，怎么说能便捷化一些。</em></p><p>那我可能有两条路子走：<br><del>1.直接在obsidian仓库下面使用git来做远程仓库，然后写好的文章直接push到github上</del>❌<br>2.借用文件同步软件，把写好的文章放到obsidian中指定的文件夹，然后单向同步到github在本机电脑中clone下来的仓库，然后用git push到远端github库上。<br>两种方式都试试吧。<br>git部分操作参考了<a href="https://blog.csdn.net/weixin_55688821/article/details/133849556">Obsidian +Obsidian Git插件 + Gitee 自动同步笔记-CSDN博客</a></p><p>2024-06-16<br>今天安装了file tree alternative和iconize插件，前者可以聚焦文件夹，后者可以添加图标，美化一下，明天操作下。</p><p>2024-06-17<br>今天把File tree设置下，设置完成之后确实就影响不大了，到时候直接复制到<code>_post</code>文件夹下，git push就好。<br>然后下载了几个图标，设置了下，但是貌似file tree不能显示自定义图标，有点尴尬。</p><p>对了，因为每次打开有视频链接的文章都会自动播放，有点烦人，试了下网上说的修改<code>&lt;iframe&gt;</code>标签为<code>&lt;vedio&gt;</code>标签，实测没有效果，增加<code>sandbox=&quot;&quot;</code>也不行。最后算了，世上无难事，只要肯放弃~😜</p><p>两条同步路子的第一条，果然会把各种其他修改全都给过commit上去，算了，我还是用文件同步吧。<br>下载安装freefilesync，设置好单项同步，设置好路径，它会自动比较差异，点击更新就可以了。完成后可以保存成批处理命令，然后用实时监控方式，这也很美妙了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>现在来回顾下，我自己的需求：</p><table><thead><tr><th><center><strong>需求</strong></center></th><th><center><strong>实现方式</strong></center></th></tr></thead><tbody><tr><td><center>日历视图</center></td><td>tasks+task calendar wrap+kanban</td></tr><tr><td><center>手机端，电脑端多端多地同步</center></td><td>remotely save中的webdav</td></tr><tr><td><center>可以通过系统（pc和ios）提醒代办事项</center></td><td>reminder插件</td></tr><tr><td><center>有知识链接图谱</center></td><td>obsidian自带的双链</td></tr><tr><td><center>可以联动hexo发布博客</center></td><td>git插件上传github仓库</td></tr></tbody></table><p>ok，obsidian可以满足我的需求，而obsidian还在不断进化，类似dataview功能还可以用代码方式自定义更高程度的展示页面，目前这篇学习记录就算完成了，后面再慢慢进步改善吧。</p>]]>
    </content>
    <id>https://ainavigation.org/2024/06/07/%E7%94%A8Obsidian%E8%AE%B0%E5%BD%95%E5%AD%A6%E4%B9%A0Obsidian/</id>
    <link href="https://ainavigation.org/2024/06/07/%E7%94%A8Obsidian%E8%AE%B0%E5%BD%95%E5%AD%A6%E4%B9%A0Obsidian/"/>
    <published>2024-06-07T05:29:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="需求以及笔记软件的比较"><a href="#需求以及笔记软件的比较" class="headerlink" title="需求以及笔记软件的比较"></a>需求以及笔记软件的比较</h2><p>首先markdown语法真的好用，我也是typora的付费用户，ty]]>
    </summary>
    <title>用Obsidian记录学习Obsidian</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="kali" scheme="https://ainavigation.org/tags/kali/"/>
    <category term="android" scheme="https://ainavigation.org/tags/android/"/>
    <content>
      <![CDATA[<h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><ul><li>最新的kali镜像</li><li>AOSP</li><li>以及一颗耐心，能慢慢解决冒出问题的心</li></ul><h2 id="安装Kali"><a href="#安装Kali" class="headerlink" title="安装Kali"></a>安装Kali</h2><p>新版的kali系统可以说真的是很神仙了，首先用这个系统的人大部分都是对linux有一定了解，并且想要学习或者会使用系统内自带的各种网络工具。</p><p>我是目前在从事网络方面的工作，所以就选择了kali，为啥会需要Android系统呢，主要是工作相关这里就不细说了，网上有ubuntu以及arch编译Android系统的，但是没有kali的，我也只是记录下自己折腾这些的过程，以后需要可以找到。</p><p>大家都知道Android源码就是在ubuntu上编译的，所以ubuntu肯定是可以的，而kali使用的是和ubuntu一样的包管理器，其实都是使用的debain。所以理论上肯定也是可以的，但是ubuntu编译Android源码就有各种问题，系统版本不同需要安装的依赖包都不一样，而且还有32位包的问题，而作为一个搞网络的为啥不在kali上搞Android呢？于是就有了这次折腾。</p><h3 id="制作U盘启动盘"><a href="#制作U盘启动盘" class="headerlink" title="制作U盘启动盘"></a>制作U盘启动盘</h3><p>好了废话不多说，安装kali系统，我用的是最新的kali系统（202006）然后制作一个安装盘，windows系统可以使用win32diskmager或者USBwriter，linux就dd就完事了。</p><h3 id="安装kali"><a href="#安装kali" class="headerlink" title="安装kali"></a>安装kali</h3><p>使用刚制作好的U盘启动盘，启动需要安装的电脑，按照引导，一步步走就好了。</p><p>这里说几个容易弯路的点：</p><ol><li>硬盘分区，其实很简单，就按照引导给你分的也行。非要自己分区的，那至少有一个boot分区，其他只要别瞎分就行了，然后就是一定要做好数据备份，装系统基本都会格式化。</li><li>双系统引导，kali会引导你写入MBR，自己注意点，真的被改写了无法启动也不要急毕竟GRUB是可以用的，可以重新设定好引导就行了，但是如果一直不行，就上网找找双系统启动的办法。</li><li>联网，新版kali是安装时候希望你联网的，可以帮你把完整的最新的一些软件包安装上，如果因为什么原因就是没有网也是可以安装一个不依赖网络的系统。</li></ol><p>安装好之后启动就可以看到kali的龙标记，自带的Xfce真好看，暗系风格。看到kali龙的标记出来，并且带着光晕动画，真的是爱了爱了。</p><h3 id="安装环境"><a href="#安装环境" class="headerlink" title="安装环境"></a>安装环境</h3><p>安装必要的一些软件啥的比如后面要用的Android Studio，ssh，virtualbox等等。</p><h4 id="修改软件源"><a href="#修改软件源" class="headerlink" title="修改软件源"></a>修改软件源</h4><p>首先先修改软件源：</p><p><code>sudo mousepad /etc/apt/sources.list</code></p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">#阿里云</span></span><br><span class="line">deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib</span><br><span class="line">deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib</span><br><span class="line"> </span><br><span class="line"><span class="comment">#清华大学</span></span><br><span class="line">deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free</span><br><span class="line">deb-src https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free</span><br><span class="line"> </span><br><span class="line"><span class="comment">#中科大</span></span><br><span class="line">deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib</span><br><span class="line">deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib</span><br><span class="line">     </span><br><span class="line"><span class="comment">#浙江大学</span></span><br><span class="line">deb http://mirrors.zju.edu.cn/kali kali-rolling main contrib non-free</span><br><span class="line">deb-src http://mirrors.zju.edu.cn/kali kali-rolling main contrib non-free</span><br><span class="line">     </span><br><span class="line"><span class="comment">#kali官方</span></span><br><span class="line">deb http://http.kali.org/kali kali-rolling main non-free contrib</span><br><span class="line">deb-src http://http.kali.org/kali kali-rolling main non-free contrib</span><br></pre></td></tr></table></figure><p>将上面的源添加到文件中保存即可，然后运行下面的命令更新：</p><p><code>sudo apt-get update</code></p><p>然后安装大部分软件都是可以的了。</p><h4 id="安装中文输入法"><a href="#安装中文输入法" class="headerlink" title="安装中文输入法"></a>安装中文输入法</h4><p>安装ibus拼音，linux发行版一般都没有中文输入法，有人会问为啥不装搜狗，emmm，我公司网络限制。</p><p><code>apt-get install ibus ibus-pinyin  </code></p><p>这里需要提到添加PPA源，刚装好的系统会出现<code> add-apt-repository:command not found</code></p><p><code>sudo apt-get install software-properties-common</code></p><p>使用上面这个命令可以解决。</p><h4 id="调整系统显示时间"><a href="#调整系统显示时间" class="headerlink" title="调整系统显示时间"></a>调整系统显示时间</h4><p>不知道是公司网络问题还是别的原因，安装系统后时间显示一直不正确。</p><p><code>sudo apt-get install ntpdate</code></p><p><code>sudo hwclock -r</code>和<code>date</code>将硬件时间和系统时间都输出看下，如果只是系统时间不正确就可以使用同步把时间调整，而我是两者时间都不对，于是将Shanghai设置到<code>etc/localtime</code></p><p><code>rm -rf /etc/localtime</code></p><p><code>cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</code></p><p>但是无论使用ntp的哪个命令都无法正确更新时间</p><p><code>ntpdate -u ntp.api.bz</code></p><p><code>ntpdate time.windows.com </code></p><p>结果都是找不到服务器，无法使用ntp同步时间，只好手动修改系统时间</p><p><code>sudo date -s &quot;20200610 16:00:00&quot;</code></p><p>为了重启后还能读取到这个时间，把它写入硬件时间</p><p><code>sudo hwclock -w</code></p><p>完成后过一会儿时间就变过来了，如果一直没有改变，那么重启下电脑。</p><h4 id="安装VirtualBox"><a href="#安装VirtualBox" class="headerlink" title="安装VirtualBox"></a>安装VirtualBox</h4><p>对于我们大多数人来说就算是办公也是需要微信之类的，网页当然也行，但是还是会有用到windows的地方，因此会需要安装虚拟机，这里能提一下虚拟机的一个坑</p><p>安装完成后启动virtualbox，提示<code>Kernel driver not installed(rc=-1908)</code>并且同时提示你使用<code>modprobe vboxdrv</code>尝试解决问题。但是当时你使用了这个命令后，又会提示你<code>modprobe: FATAL: Module vboxdrv not found in directory /lib/modules/***********</code>这个时候最先考虑是该整体更新系统了或者是BISO安全启动的问题，然后才是是不是virtualbox的服务没有启动。</p><ul><li><p><code>sudo apt install virtualbox-dkms</code>重新安装virtualbox-dkms或<code>sudo apt upgrade</code>更新整个系统</p></li><li><p>引导到BIOS并进入&gt; advanced (f7) &gt; boot &gt; scroll down to” secure boot” &gt; 更改”Windows EUFI mode” to” other OS” &gt; advanced (f7) &gt; boot  &gt; scroll down to” secure boot” &gt; 更改”Windows EUFI mode” to” other  OS”</p></li></ul><h2 id="编译Android源码"><a href="#编译Android源码" class="headerlink" title="编译Android源码"></a>编译Android源码</h2><p>首先是下载源码，这个推荐清华的镜像</p><h3 id="安装编译依赖库"><a href="#安装编译依赖库" class="headerlink" title="安装编译依赖库"></a>安装编译依赖库</h3><p>解压之后先别着急开始编译，需要先安装依赖库，不过依赖库里有很多都是32位的，需要先开启32位兼容模式：<code>sudo dpkg --add-architecture i386</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install libx11-dev:i386 libreadline-dev:i386 libgl1-mesa-dev g++-multilib lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc unzip m4 lib32ncurses5-dev x11proto-core-dev libx11-dev  libc6-dev-i386  zip curl zlib1g-dev gcc-multilib g++-multilib  git-core gnupg flex bison gperf build-essential  dpkg-dev libsdl1.2-dev libesd0-dev tofrodos python3-markdown libxml2-utils xsltproc zlib1g-dev:i386  git flex bison gperf build-essential libncurses5-dev:i386  libswitch-perl libxml2-utils</span><br></pre></td></tr></table></figure><p>其中有一些库提示已经有兼容库了，那就直接替换。然后还有些库是找不到的，比如：<code>libesd0-dev</code>。那就上debain包的官网上去找<a href="https://packages.debian.org/jessie/libesd0-dev">https://packages.debian.org/jessie/libesd0-dev</a>，记得把依赖也安装上。</p><p><code>dpkg -i libesd0-dev_0.2.41-11_amd64.deb</code></p><h3 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h3><p><code>source build/envsetup.sh</code></p><p><code>lunch</code></p><p><code>make -j8 2&gt;&amp;1 |tee build.log</code></p><p>根据线程数自己定义线程数，比如你电脑是4核8线程的cpu就可以用j8，但是会很卡，可以保留2-4个线程来干点别的事情。</p><p>然后就是漫长的等待</p><h3 id="导入AndroidStudio"><a href="#导入AndroidStudio" class="headerlink" title="导入AndroidStudio"></a>导入AndroidStudio</h3><p>这个呢就看是否需要了，我其实就是用来看看源码，找找代码用。</p><p>在刚才source lunch过的bash shell中继续操作</p><p><code>mmm development/tools/idegen/</code></p><p><code>development/tools/idegen/idegen.sh</code></p><p>修改Android.iml文件把不需要的文件夹排除出去</p><p>然后导入AndroidStudio</p>]]>
    </content>
    <id>https://ainavigation.org/2020/06/12/%E5%9C%A8Kali%E4%B8%8A%E7%BC%96%E8%AF%91Android%E7%B3%BB%E7%BB%9F/</id>
    <link href="https://ainavigation.org/2020/06/12/%E5%9C%A8Kali%E4%B8%8A%E7%BC%96%E8%AF%91Android%E7%B3%BB%E7%BB%9F/"/>
    <published>2020-06-12T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><ul>
<li>最新的kali镜像</li>
<li>AOSP</li>
<li>以及一颗耐心，能慢慢解决冒出问题的心</li>]]>
    </summary>
    <title>在Kali上编译Android系统</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="linux" scheme="https://ainavigation.org/tags/linux/"/>
    <category term="android" scheme="https://ainavigation.org/tags/android/"/>
    <category term="battery historian" scheme="https://ainavigation.org/tags/battery-historian/"/>
    <category term="bugreport" scheme="https://ainavigation.org/tags/bugreport/"/>
    <content>
      <![CDATA[<p>由于工作需要功耗问题上总是需要使用Battery Historian来解析Bugrepot，这里呢就把这个过程整理下。而根据官网Docker是最方便的方式，但是由于公司网络大多有不少限制，因此我没能用成，只能一步步把运行环境全部搭建起来。</p><h2 id="安装运行环境"><a href="#安装运行环境" class="headerlink" title="安装运行环境"></a>安装运行环境</h2><h3 id="GO语言"><a href="#GO语言" class="headerlink" title="GO语言"></a>GO语言</h3><p>首先到GO的官网上下载最新版本的go安装包，至于linux如何安装也有文档可以查询。</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">tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz</span><br></pre></td></tr></table></figure><p>在mac上使用官网下载的go语言安装包安装后</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></pre></td><td class="code"><pre><span class="line">export PATH=$PATH:/usr/local/go/bin</span><br><span class="line">export GOPATH=$HOME/go</span><br><span class="line">export GOBIN=$GOPATH/bin</span><br><span class="line">export PATH=$PATH:$GOBIN</span><br></pre></td></tr></table></figure><p>这里注意要设置<code>GOPATH</code>的路径，否则后续编译安装Battery Historian使用到的时候会找不到路径。</p><h3 id="安装Python2-7"><a href="#安装Python2-7" class="headerlink" title="安装Python2.7"></a>安装Python2.7</h3><p>这个一般linux系统都有，如果是python3的话，记得设置切换到2.7。</p><p>如果没有呢，可以去<a href="https://python.org/downloads%E5%AE%98%E7%BD%91%E4%B8%8B%E8%BD%BD%E3%80%82">https://python.org/downloads官网下载。</a></p><h3 id="安装java运行环境"><a href="#安装java运行环境" class="headerlink" title="安装java运行环境"></a>安装java运行环境</h3><p>从官网安装 <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">http://www.oracle.com/technetwork/java/javase/downloads/index.html</a>.</p><h2 id="安装Battery-Historian"><a href="#安装Battery-Historian" class="headerlink" title="安装Battery Historian"></a>安装Battery Historian</h2><p>官网上又是很简单的</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> get -d -u github.com/google/battery-historian/...</span><br></pre></td></tr></table></figure><p>但是非常不幸，我这里还是因为网络的问题无法安装。不要问我为什么github都不能访问。</p><p>需要安装三个东西</p><ul><li><p><a href="http://github.com/golang/protobuf/proto">proto</a></p></li><li><p><a href="https://github.com/google/closure-compiler">closure-compiler</a></p></li><li><p><a href="https://github.com/golang/protobuf">protobuf</a></p></li></ul><p>安装好之后先运行安装编译：</p><figure class="highlight go"><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 $GOPATH/src/github.com/google/battery-historian</span><br><span class="line"><span class="keyword">go</span> run setup.<span class="keyword">go</span></span><br></pre></td></tr></table></figure><p>基本应该是没啥问题了，如果还有报错就再看下，是不是少了<code>historian-optimized.js</code>文件<br>下载地址：<code>链接：pan.baidu.com/s/1kFdUVM6ICT_3Uh1ui14J3w 提取码：3fnn</code><br>下载后放到<code>\go\src\github.com\google\battery-historian\compiled</code>目录下</p><p>如果是连接了可以上google的网络，则可以不用单独安装，在<code>go get -d -u github.com/google/battery-historian/...</code>之后就可以直接</p><figure class="highlight go"><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 $GOPATH/src/github.com/google/battery-historian</span><br><span class="line"><span class="keyword">go</span> run setup.<span class="keyword">go</span></span><br></pre></td></tr></table></figure><p>但是目前（2020-10-21）<code>closure-library</code>有问题，会导致<code>go run setup.go</code>报错，解决办法如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//go run setup.go (this fails)</span></span><br><span class="line">cd third_party/closure-library/</span><br><span class="line">git reset --hard v20170409</span><br><span class="line">cd -</span><br><span class="line"><span class="keyword">go</span> run setup.<span class="keyword">go</span> <span class="comment">//(this passes)</span></span><br></pre></td></tr></table></figure><h2 id="运行Battery-Historian"><a href="#运行Battery-Historian" class="headerlink" title="运行Battery Historian"></a>运行Battery Historian</h2><figure class="highlight go"><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 $GOPATH/src/github.com/google/battery-historian</span><br><span class="line"><span class="keyword">go</span> run cmd/battery-historian/battery-historian.<span class="keyword">go</span> [--port &lt;<span class="keyword">default</span>:<span class="number">9999</span>&gt;]</span><br></pre></td></tr></table></figure><p>这个时候大概率是无法运行的，或者是出现等了好久出现了界面，但是选择了需要解析的文件后没有submit提交按钮。</p><p>这个时候需要在<code>$GOPATH\src\github.com\google\battery-historian\templates\base.html</code>中替换js文件的加载地址。</p><p>可以从<a href="https://www.bootcdn.cn/">https://www.bootcdn.cn/</a>网站上找到对应需要替换的js文件（尽量版本也可以对应）</p><p>替换后打开快了吗？不会！！</p><p>浏览器中开发者模式下，你会发现访问了<a href="https://www.google.com/jsapi">https://www.google.com/jsapi</a>，这个是用来查询js的google的api，但是由于我们已经替换过了，并且国内无法使用google的域名，怎么办呢？嘿嘿，干掉它，注释掉。或者改为cn域名就可以访问了，但是如果你完全做了替换那也就没啥意义了。</p><p>附上<code>base.html</code>的前半部分有修改的地方。</p><figure class="highlight html"><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/jqueryui/1.11.4/jquery-ui.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/jquery/1.11.2/jquery.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/select2/3.5.4/select2.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/jquery-contextmenu/1.6.6/jquery.contextMenu.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;//cdn.datatables.net/1.10.9/css/jquery.dataTables.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/select2/3.5.4/select2.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/jquery-contextmenu/1.6.6/jquery.contextMenu.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.datatables.net/1.10.9/js/jquery.dataTables.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/moment.js/2.13.0/moment.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">&lt;!-- &lt;script type=&quot;text/javascript&quot; src=&quot;https://www.google.com/jsapi&quot;&gt;&lt;/script&gt; --&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Loading fonts with https://www.gstatic.com/external_hosted/twitter_bootstrap_css/css/bootstrap.min.css is blocked by CORS policy, which means icons won&#x27;t load. --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/flot/0.8.3/jquery.flot.min.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;third_party/flot-axislabels/jquery.flot.axislabels.js?ver=&#123;&#123;.ResVersion&#125;&#125;&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//www.benjaminbuffet.com/public/js/jquery.flot.orderBars.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/flot/0.8.3/jquery.flot.pie.min.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;static/stylesheet.css?ver=&#123;&#123;.ResVersion&#125;&#125;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;static/historian.css?ver=&#123;&#123;.ResVersion&#125;&#125;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;static/histogram.css?ver=&#123;&#123;.ResVersion&#125;&#125;&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;//www.gstatic.com/external_hosted/jquery_form/jquery.form.min.js&quot;</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span> <span class="attr">src</span>=<span class="string">&quot;//cdn.bootcdn.net/ajax/libs/d3/4.9.1/d3.min.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    &#123;&#123; if .IsOptimizedJs &#125;&#125;</span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;compiled/historian-optimized.js?ver=&#123;&#123;.ResVersion&#125;&#125;&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    &#123;&#123; else &#125;&#125;</span><br><span class="line">    <span class="comment">&lt;!-- Need to load Closure in header. --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"><span class="variable constant_">CLOSURE_NO_DEPS</span>=<span class="literal">true</span></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span> <span class="attr">src</span>=<span class="string">&quot;third_party/closure-library/closure/goog/base.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span> <span class="attr">src</span>=<span class="string">&quot;compiled/historian_deps-runfiles.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    &#123;&#123; end &#125;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Battery Historian<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个时候再运行就应该很快了。</p><h3 id="点击error按钮不显示解析报错的问题"><a href="#点击error按钮不显示解析报错的问题" class="headerlink" title="点击error按钮不显示解析报错的问题"></a>点击error按钮不显示解析报错的问题</h3><p>不知道为啥目前可以解析，但是如果读取一个AndroidQ的bugreport会报错，但是点击error按钮却无法显示报错信息。目前在mac上构建的可以正常显示报错信息。后期如果解决了该问题再更新。</p><h2 id="解析bugreport"><a href="#解析bugreport" class="headerlink" title="解析bugreport"></a>解析bugreport</h2><h3 id="抓取Bugreport"><a href="#抓取Bugreport" class="headerlink" title="抓取Bugreport"></a>抓取Bugreport</h3><p>由于版本问题不同方式抓取的bugreport会有不同的问题，所以抓取的时候，尽量按照官网的方式抓取，要重置汇总的电池统计信息和历史记录：</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">adb shell dumpsys batterystats --reset</span><br></pre></td></tr></table></figure><h4 id="唤醒锁分析"><a href="#唤醒锁分析" class="headerlink" title="唤醒锁分析"></a>唤醒锁分析</h4><p>默认情况下，Android不记录特定于应用程序的时间戳，即使在运行的基础上维护了汇总统计信息，用户空间唤醒锁的转换。 如果要Historian显示有关的详细信息，时间轴上的每个唤醒锁，都应启用完整唤醒锁，开始实验之前，请使用以下命令进行报告：</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">adb shell dumpsys batterystats --enable full-wake-history</span><br></pre></td></tr></table></figure><p>请注意，通过启用完整的唤醒锁报告，电池历史记录日志将在几个小时后溢出。 使用此选项可以进行短时间的测试（3-4小时）。</p><h4 id="内核跟踪日志分析"><a href="#内核跟踪日志分析" class="headerlink" title="内核跟踪日志分析"></a>内核跟踪日志分析</h4><p>生成跟踪文件来记录内核唤醒源和内核唤醒锁活动：</p><p>首先，将设备连接到台式机&#x2F;笔记本电脑并启用内核跟踪日志记录：</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><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">adb root</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">adb shell</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Set the events to trace.</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;power:wakeup_source_activate&quot;</span> &gt;&gt; /d/tracing/set_event</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> <span class="string">&quot;power:wakeup_source_deactivate&quot;</span> &gt;&gt; /d/tracing/set_event</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">The default trace size <span class="keyword">for</span> most devices is 1MB, <span class="built_in">which</span> is relatively low and might cause the logs to overflow.</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">8MB to 10MB should be a decent size <span class="keyword">for</span> 5-6 hours of logging.</span></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 class="built_in">echo</span> 8192 &gt; /d/tracing/buffer_size_kb</span></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 class="built_in">echo</span> 1 &gt; /d/tracing/tracing_on</span></span><br></pre></td></tr></table></figure><p>然后，将该设备用于预期的测试用例。</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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">echo</span> 0 &gt; /d/tracing/tracing_on</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">adb pull /d/tracing/trace &lt;some path&gt;</span></span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Take a bug report at this time.</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">adb bugreport &gt; bugreport.txt</span></span><br></pre></td></tr></table></figure><p>Note:在输入完<code>adb shell dumpsys batterystats --reset</code>这个命令后bugreport就会清空了之前的信息，并且重新开始记录电量信息，所以在开始测试前就拔掉USB数据线，在测试步骤完成后，重新插上usb数据线，再输入<code>adb bugreport bugreport.zip</code>就会生成到你电脑的当前目录。</p>]]>
    </content>
    <id>https://ainavigation.org/2020/06/12/%E5%9C%A8Linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E5%AE%89%E8%A3%85Battery%20Historian%E8%A7%A3%E6%9E%90Bugreport/</id>
    <link href="https://ainavigation.org/2020/06/12/%E5%9C%A8Linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E5%AE%89%E8%A3%85Battery%20Historian%E8%A7%A3%E6%9E%90Bugreport/"/>
    <published>2020-06-12T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>由于工作需要功耗问题上总是需要使用Battery Historian来解析Bugrepot，这里呢就把这个过程整理下。而根据官网Docker是最方便的方式，但是由于公司网络大多有不少限制，因此我没能用成，只能一步步把运行环境全部搭建起来。</p>
<h2 id="安装运行环]]>
    </summary>
    <title>在Linux系统上安装Battery Historian解析Bugreport</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Tools" scheme="https://ainavigation.org/categories/Tools/"/>
    <category term="git" scheme="https://ainavigation.org/tags/git/"/>
    <content>
      <![CDATA[<p>首先区分git是分布式代码管理系统，每个计算机上或者说每个克隆的git仓库都是完整的，因此同一个本地克隆的远程的git仓库的关系是，本地对应远程（远端，非本机的），因此git的命令行也就对应了本地仓库的操作和同步到远程仓库的操作。</p><h2 id="git基础配置"><a href="#git基础配置" class="headerlink" title="git基础配置"></a>git基础配置</h2><p>假设在git服务器上设置的用户名是：gituser1，邮箱是：<a href="mailto:&#103;&#x69;&#x74;&#117;&#x73;&#101;&#114;&#x31;&#64;&#x67;&#x69;&#116;&#46;&#x63;&#111;&#x6d;">gituser1@git.com</a></p><h3 id="配置git提交时候的全局用户名"><a href="#配置git提交时候的全局用户名" class="headerlink" title="配置git提交时候的全局用户名"></a>配置git提交时候的全局用户名</h3><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">gitconfig --global user.name gituser1</span><br></pre></td></tr></table></figure><h3 id="配置git提交时候的全局邮箱"><a href="#配置git提交时候的全局邮箱" class="headerlink" title="配置git提交时候的全局邮箱"></a>配置git提交时候的全局邮箱</h3><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">git config --global user.email gituser1@git.com</span><br></pre></td></tr></table></figure><h3 id="配置gerrit的review"><a href="#配置gerrit的review" class="headerlink" title="配置gerrit的review"></a>配置gerrit的review</h3><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">git config --global review.gerrit.example.com:8081.username yourname</span><br></pre></td></tr></table></figure><h2 id="常用仓库操作"><a href="#常用仓库操作" class="headerlink" title="常用仓库操作"></a>常用仓库操作</h2><h3 id="创建git库"><a href="#创建git库" class="headerlink" title="创建git库"></a>创建git库</h3><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">git init</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></pre></td><td class="code"><pre><span class="line">git branch</span><br></pre></td></tr></table></figure><p>想要查看对应的远端仓库分支可以用<code>git branch -a</code>来查看</p><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></pre></td><td class="code"><pre><span class="line">git checkout -b master master_copy</span><br></pre></td></tr></table></figure><p>mater对应是远程仓库的分支名，master_copy是我们需要创建的本地仓库的分支名</p><h3 id="查看当前git库状态"><a href="#查看当前git库状态" class="headerlink" title="查看当前git库状态"></a>查看当前git库状态</h3><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">git status</span><br></pre></td></tr></table></figure><p>显示出红色的文件代表当前仓库中有文件与上一次同步状态不同<br>如果没有文件显示，且没有更改，会显示当前分支与远端分支一致，没有需要提交的更改。<br>如果显示没有更改，但是当前分支与远端分支不同步，则需要同步远端分支</p><p><em>养成良好的习惯提交修改代码前先把本地仓库与远端仓库同步</em></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">git remote -v</span><br></pre></td></tr></table></figure><p>查看当前git仓库对应的远程git仓库</p><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></pre></td><td class="code"><pre><span class="line">git pull</span><br></pre></td></tr></table></figure><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">git pull --rebase</span><br></pre></td></tr></table></figure><p> 这个命令会把本地仓库换一个基础，可以认为是仓库是需要一个地皮的，rebase的作用就是把仓库换个干净的地皮重新造一个一样的与远程一样的仓库，　这样新的仓库就与远端的仓库完全一致了。</p><p>但是还有情况会出现本地仓库领先与远端仓库，而我们又不需要本地的修改，就可以把远端仓库检查校对拷贝过来。比如文件名为temp.log（相当于是把本地改动回退到远端文件版本）</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">git checkout　temp.log</span><br></pre></td></tr></table></figure><p>将当前文件夹内所有文件都回退到远端git仓库</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">git checkout .</span><br></pre></td></tr></table></figure><hr><p>当修改了名为temp.log的文件后</p><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></pre></td><td class="code"><pre><span class="line">git diff temp.log</span><br></pre></td></tr></table></figure><p> 终端会显示出该文件的修改点，对应增删，如果开启了git的高亮显示，会有绿色表示添加，红色表示删除</p><p> 可以使用<code>git config --global color.ui true</code>来开启git颜色高亮</p><h3 id="添加文件-该文件在当前目录下-到当前git仓库（确切说是添加到了本地仓库的提交暂存区）"><a href="#添加文件-该文件在当前目录下-到当前git仓库（确切说是添加到了本地仓库的提交暂存区）" class="headerlink" title="添加文件(该文件在当前目录下)到当前git仓库（确切说是添加到了本地仓库的提交暂存区）"></a>添加文件(该文件在当前目录下)到当前git仓库（确切说是添加到了本地仓库的提交暂存区）</h3><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">git add temp.log</span><br></pre></td></tr></table></figure><p>将当前文件夹内所有文件都添加到当前git仓库</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">git add .</span><br></pre></td></tr></table></figure><p> 将文件添加到当前git仓库后，再使用<code>git status</code>查看状态，会发现文件对应路径会从红色变成绿色。</p><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></pre></td><td class="code"><pre><span class="line">git commit temp.log</span><br></pre></td></tr></table></figure><p>&amp;</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">git commit .</span><br></pre></td></tr></table></figure><p>&amp;</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">git commit temp.log -m &quot;comit message(此引号内应填入提交信息)&quot;</span><br></pre></td></tr></table></figure><p> 提交后可以可以通过<code>git log</code>命令查看这个git仓库的历史记录</p><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></pre></td><td class="code"><pre><span class="line">git log</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></pre></td><td class="code"><pre><span class="line">git reset HEAD^</span><br></pre></td></tr></table></figure><p> 解释下这个命令，reset本身可以重置，而HEAD表示分支的头，^表示分支头部的上一个位置，所以是回退到历史记录的上一条，也就是说如果你<code>git pull</code>后本地没有修改，但是这个时候执行了本条命令，那么你当前的本地仓库还是会回到历史记录中的上一个位置（尽管这个提交不是你操作的）</p><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></pre></td><td class="code"><pre><span class="line">git commit --amend</span><br></pre></td></tr></table></figure><p>可以使用<code>git reset HEAD^</code>命令使当前仓库回退到历史记录中上次的提交状态，这个时候比如说我们的temp.log已经commit后，使用这个resset命令会让我们的commit失效，文件状态会变成add之前的状态，这个时候我们可以修改temp.log然后使用<code>git commit --amend</code>合并上一次提交。</p><h2 id="其他git命令（进阶）"><a href="#其他git命令（进阶）" class="headerlink" title="其他git命令（进阶）"></a>其他git命令（进阶）</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></pre></td><td class="code"><pre><span class="line">git show HEAD^</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></pre></td><td class="code"><pre><span class="line">git show dfb02e6e4f2f7b573337763e5c0013802e392818</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></pre></td><td class="code"><pre><span class="line">git diff HEAD^</span><br></pre></td></tr></table></figure><h3 id="显示所有已添加index（暂存）但还未commit的变更"><a href="#显示所有已添加index（暂存）但还未commit的变更" class="headerlink" title="显示所有已添加index（暂存）但还未commit的变更"></a>显示所有已添加index（暂存）但还未commit的变更</h3><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">git diff --cached</span><br></pre></td></tr></table></figure><h3 id="切换到dev分支（当前不在dev分支）"><a href="#切换到dev分支（当前不在dev分支）" class="headerlink" title="切换到dev分支（当前不在dev分支）"></a>切换到dev分支（当前不在dev分支）</h3><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">git checkout dev</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></pre></td><td class="code"><pre><span class="line">git fetch</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></pre></td><td class="code"><pre><span class="line">git branch -d dev</span><br></pre></td></tr></table></figure><p>强制删除 <code>git branch -D dev</code></p><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></pre></td><td class="code"><pre><span class="line">git show-branch</span><br></pre></td></tr></table></figure><p>图示所有分支<code>git show-branch -all</code></p><h3 id="单独提交到有gerrit系统的代码审核"><a href="#单独提交到有gerrit系统的代码审核" class="headerlink" title="单独提交到有gerrit系统的代码审核"></a>单独提交到有gerrit系统的代码审核</h3><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">git push [remote_alias] HEAD:refs/for/[branch_name]</span><br></pre></td></tr></table></figure><p>替换<code>[remote_alias]</code>和<code>[branch_name]</code></p><p> remote_alias查看方法： <code>git remote -v</code></p><p> <strong>千万不要使用<code>git push  [branch_name]:origin/master</code></strong></p><h3 id="查看git仓库中文件某些行代码的最新提交信息"><a href="#查看git仓库中文件某些行代码的最新提交信息" class="headerlink" title="查看git仓库中文件某些行代码的最新提交信息"></a>查看git仓库中文件某些行代码的最新提交信息</h3><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">git blame -L 25,+5 xxxx.java</span><br></pre></td></tr></table></figure><p>   -L 参数表示以行数参数查看，25表示想要查看的行数，+5表示从25行开始看到30行的代码</p>]]>
    </content>
    <id>https://ainavigation.org/2019/09/18/git%20%E4%BB%A3%E7%A0%81%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</id>
    <link href="https://ainavigation.org/2019/09/18/git%20%E4%BB%A3%E7%A0%81%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/"/>
    <published>2019-09-18T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>首先区分git是分布式代码管理系统，每个计算机上或者说每个克隆的git仓库都是完整的，因此同一个本地克隆的远程的git仓库的关系是，本地对应远程（远端，非本机的），因此git的命令行也就对应了本地仓库的操作和同步到远程仓库的操作。</p>
<h2 id="git基础配置">]]>
    </summary>
    <title>git 代码管理工具常用命令</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="LCD" scheme="https://ainavigation.org/tags/LCD/"/>
    <category term="BSP" scheme="https://ainavigation.org/tags/BSP/"/>
    <content>
      <![CDATA[<p>由于公司项目比较急，我也是刚开始接触源码上修改代码，但是正因如此这次的需求来了之后对Android屏幕亮度曲线调节有了一个新的认知，记录下来也算时一种经验分享。我会从自动和手动两个方面来说明，最后在补充一些使用的测试仪器时候的一些心得吧。</p><h2 id="对Android源码背光亮度方面的研究"><a href="#对Android源码背光亮度方面的研究" class="headerlink" title="对Android源码背光亮度方面的研究"></a>对Android源码背光亮度方面的研究</h2><p>公欲善其事，必先利其器。所以首先我们先来看下源码。同样我们也时分为两个部分，手动和自动。至于为啥要分开说，其实研究过源码后，其实就知道了，源码就是通过自动背光的开关，直接把两个部分分开的。因此首先要纠正一个错误的观点，那就是：“手动背光曲线是什么样的，开了自动背光开关后，环境光一定的情况下，滑动亮度调节进度条，这时候的曲线和手动背光时候的曲线是一样的。”这个想法时完完全全错了，手动和自动背光就是分开的，两种情况下的背光曲线没有任何关联，当然定制化的不算，我们仅说源码角度(基于Android7.1),说起来也奇怪，自动背光这部分的代码在8.0之后又调整了算法，我们暂时不管它，下面讲到的时候再具体说明。</p><h3 id="基础知识普及"><a href="#基础知识普及" class="headerlink" title="基础知识普及"></a>基础知识普及</h3><ol><li>pwm 脉冲宽度调制（占空比），基本原理：控制方式就是对逆变电路开关器件的通断进行控制，使输出端得到一系列幅值相等的脉冲，用这些脉冲来代替正弦波或所需要的波形。也就是在输出波形的半个周期中产生多个脉冲，使各脉冲的等值电压为正弦波形，所获得的输出平滑且低次谐波少。按一定的规则对各脉冲的宽度进行调制，即可改变逆变电路输出电压的大小，也可改变输出频率。</li></ol><p><img src="https://i.loli.net/2020/10/27/Qjw3lGXpCn4Iadq.png" alt="pwm"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><ol start="2"><li>gamma（屏幕灰度），灰度使用黑色调表示物体。 每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。 使用黑白或灰度扫描仪生成的图像通常以灰度显示。</li></ol><p><img src="https://i.loli.net/2020/10/27/UYRAVqr2axsEw6g.png" alt="gamma"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p><img src="https://i.loli.net/2020/10/27/sdK96MOrVJcLybF.png" alt="人眼感知与gamma值的变化"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><ol start="3"><li><p>屏幕亮度设定（0-255)，这个设定值相当于是与屏幕亮度之间的一个关系，一般当项目流程交给软件工程师来调整曲线时，这个对应关系就是确定的了。当然不同的供应商会对应有不同的对应关系，这个关系也不一定是纯线性的，一般来说会呈现如下图这样的趋势。</p></li><li><p>Android背光调整架构<br>下图是MTK平台的背光调整架构图从下至上对应硬件到软件层。</p></li></ol><p><img src="https://i.loli.net/2020/10/27/5xFEd8mrOYjvtkb.png" alt="Android MTK平台背光调整架构"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><h3 id="手动背光"><a href="#手动背光" class="headerlink" title="手动背光"></a>手动背光</h3><p>此功能在<code>settings---&gt;display---&gt;brightness</code>下面，可知有自动调节和手动调节背光亮度的功能，其中手动是通过进度条(slider)来调节的，此应用对应的布局文件为<code>\packages\apps\Settings\res\layout\preference_dialog_brightness.xml </code>如果项目有overlay请自行找下对应复写布局</p><p>在<code>\frameworks\base\core\res\res\values\config.xml</code>下定义了手动背光亮度的最小值</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Minimumscreen brightness allowed by the power manager. --&gt;</span>  </span><br><span class="line">&lt;integernameintegername=&quot;config_screenBrightnessDim&quot;&gt;20<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br></pre></td></tr></table></figure><p>在<code>\frameworks\base\core\java\android\os\Power.java</code>中定义：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> * Brightness value for fully off </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">final</span> <span class="type">int</span> <span class="variable">BRIGHTNESS_OFF</span> <span class="operator">=</span> <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"> * Brightness value for dim backlight </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">final</span> <span class="type">int</span> <span class="variable">BRIGHTNESS_DIM</span> <span class="operator">=</span><span class="number">20</span>;  </span><br><span class="line"></span><br><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> * Brightness value for fully on </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">final</span> <span class="type">int</span> <span class="variable">BRIGHTNESS_ON</span> <span class="operator">=</span><span class="number">255</span>;  </span><br><span class="line"></span><br><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> * Brightness value to use when battery islow </span></span><br><span class="line"><span class="comment"> */</span>  </span><br><span class="line"><span class="keyword">public</span> staticfinal <span class="type">int</span> <span class="variable">BRIGHTNESS_LOW_BATTERY</span> <span class="operator">=</span> <span class="number">10</span>;  </span><br></pre></td></tr></table></figure><p>在<code>\frameworks\base\packages\SettingsProvider\res\values\defaults.xml</code>中定义了默认值</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></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Default screen brightness, from 0 to 255.  102 is 40%. --&gt;</span></span><br><span class="line">&lt;integernameintegername=&quot;def_screen_brightness&quot;&gt;102<span class="tag">&lt;/<span class="name">integer</span>&gt;</span>  </span><br><span class="line">&lt;boolnameboolname=&quot;def_screen_brightness_automatic_mode&quot;&gt;false<span class="tag">&lt;/<span class="name">bool</span>&gt;</span>  </span><br></pre></td></tr></table></figure><p>由这里的代码我们可以看到亮度调节范围是从20-255.这里我们需要注意的时屏幕的最小亮度不能设置为0，0代表的含义是息屏，也就是屏幕完全不亮，允许用户调节的时候一旦设置成了0，就会导致屏幕直接黑了且无法再触控调整。</p><p>刚才我们找到了布局，所以我们看下<code>/frameworks/base/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java</code>中覆写的进度条onchanged方法：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onChanged</span><span class="params">(ToggleSlider toggleSlider, <span class="type">boolean</span> tracking, <span class="type">boolean</span> automatic, <span class="type">int</span> value, <span class="type">boolean</span> stopTracking)</span> &#123;</span><br><span class="line">    updateIcon(mAutomatic);</span><br><span class="line">    <span class="keyword">if</span> (mExternalChange) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mSliderAnimator != <span class="literal">null</span>) &#123;</span><br><span class="line">        mSliderAnimator.cancel();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> min;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> max;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> metric;</span><br><span class="line">    <span class="keyword">final</span> String setting;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mIsVrModeEnabled) &#123;</span><br><span class="line">        metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;</span><br><span class="line">        min = mMinimumBacklightForVr;</span><br><span class="line">        max = mMaximumBacklightForVr;</span><br><span class="line">        setting = Settings.System.SCREEN_BRIGHTNESS_FOR_VR;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        metric = mAutomatic</span><br><span class="line">                ? MetricsEvent.ACTION_BRIGHTNESS_AUTO</span><br><span class="line">                : MetricsEvent.ACTION_BRIGHTNESS;</span><br><span class="line">        min = mMinimumBacklight;</span><br><span class="line">        max = mMaximumBacklight;</span><br><span class="line">        setting = Settings.System.SCREEN_BRIGHTNESS;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">val</span> <span class="operator">=</span> convertGammaToLinear(value, min, max);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (stopTracking) &#123;</span><br><span class="line">        MetricsLogger.action(mContext, metric, val);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    setBrightness(val);</span><br><span class="line">    <span class="keyword">if</span> (!tracking) &#123;</span><br><span class="line">        AsyncTask.execute(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                    Settings.System.putIntForUser(mContext.getContentResolver(),</span><br><span class="line">                            setting, val, UserHandle.USER_CURRENT);</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">for</span> (BrightnessStateChangeCallback cb : mChangeCallbacks) &#123;</span><br><span class="line">        cb.onBrightnessLevelChanged();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>主要的就是这个setBrightness(val);的方法，它最后是在PowerManager中去设置了settings.db数据库中的值，原理和<code>Settings.System.putIntForUser(mContext.getContentResolver(),setting, val, UserHandle.USER_CURRENT);</code>这个代码基本时一致，当然这个代码生效需要系统签名。<br>这里给出一个与可以adb shell下修改这个背光亮度值的一个方法：<br>获取当前背光亮度值（0-255）<code>cat sys/class/leds/lcd-backlight/brightness</code><br>设置背光亮度（0-255）<code>echo 102 &gt; sys/class/leds/lcd-backlight/brightness</code><br>当然这里的方法可以看到在BrightnessController.java类中定义了一个内部类BrightnessObserver，这个类的功能时时监听settings.db数据库中背光亮度的变化，发生变化后会再通过hanlder去更新进度条，将自定义的ToggleSlider设置进度条的总长和当前进度位置。因此基于这个机制我们可以认为源码的进度条调节机制是线性曲线。</p><h3 id="自动背光亮度"><a href="#自动背光亮度" class="headerlink" title="自动背光亮度"></a>自动背光亮度</h3><p>这里有一篇博客<a href="https://blog.csdn.net/kitty_landon/article/details/64128140">Android7.1 亮度自动调节</a>写的不错的，可以直接看下，我下就拿其中重点的提下就行了。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">updateAutoBrightness</span><span class="params">(<span class="type">boolean</span> sendUpdate)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mAmbientLuxValid) &#123;</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">    <span class="type">float</span> <span class="variable">value</span> <span class="operator">=</span> mScreenAutoBrightnessSpline.interpolate(mAmbientLux);<span class="comment">//从mScreenAutoBrightnessSpline中获取到当前环境光照mAmbientLux对应的屏幕亮度值与255的比值value。</span></span><br><span class="line">    <span class="type">float</span> <span class="variable">gamma</span> <span class="operator">=</span> <span class="number">1.0f</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT</span><br><span class="line">            &amp;&amp; mScreenAutoBrightnessAdjustment != <span class="number">0.0f</span>) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">float</span> <span class="variable">adjGamma</span> <span class="operator">=</span> MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma,</span><br><span class="line">                Math.min(<span class="number">1.0f</span>, Math.max(-<span class="number">1.0f</span>, -mScreenAutoBrightnessAdjustment)));<span class="comment">//pow(x,y)求x的y次方。</span></span><br><span class="line">        gamma *= adjGamma;<span class="comment">//计算gamma值，</span></span><br><span class="line">        <span class="keyword">if</span> (DEBUG) &#123;</span><br><span class="line">            Slog.d(TAG, <span class="string">&quot;updateAutoBrightness: adjGamma=&quot;</span> + adjGamma);</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> (mUseTwilight) &#123;</span><br><span class="line">        <span class="type">TwilightState</span> <span class="variable">state</span> <span class="operator">=</span> mTwilight.getLastTwilightState();</span><br><span class="line">        <span class="keyword">if</span> (state != <span class="literal">null</span> &amp;&amp; state.isNight()) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">long</span> <span class="variable">duration</span> <span class="operator">=</span> state.sunriseTimeMillis() - state.sunsetTimeMillis();</span><br><span class="line">            <span class="keyword">final</span> <span class="type">long</span> <span class="variable">progress</span> <span class="operator">=</span> System.currentTimeMillis() - state.sunsetTimeMillis();</span><br><span class="line">            <span class="keyword">final</span> <span class="type">float</span> <span class="variable">amount</span> <span class="operator">=</span> (<span class="type">float</span>) Math.pow(<span class="number">2.0</span> * progress / duration - <span class="number">1.0</span>, <span class="number">2.0</span>);</span><br><span class="line">            gamma *= <span class="number">1</span> + amount * TWILIGHT_ADJUSTMENT_MAX_GAMMA;</span><br><span class="line">            <span class="keyword">if</span> (DEBUG) &#123;</span><br><span class="line">                Slog.d(TAG, <span class="string">&quot;updateAutoBrightness: twilight amount=&quot;</span> + amount);</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">if</span> (gamma != <span class="number">1.0f</span>) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">float</span> <span class="variable">in</span> <span class="operator">=</span> value;</span><br><span class="line">        value = MathUtils.pow(value, gamma);</span><br><span class="line">        <span class="keyword">if</span> (DEBUG) &#123;</span><br><span class="line">            Slog.d(TAG, <span class="string">&quot;updateAutoBrightness: gamma=&quot;</span> + gamma</span><br><span class="line">                    + <span class="string">&quot;, in=&quot;</span> + in + <span class="string">&quot;, out=&quot;</span> + value);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">newScreenAutoBrightness</span> <span class="operator">=</span></span><br><span class="line">            clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));<span class="comment">//round()四舍五入，计算当前环境光照实际亮度值</span></span><br><span class="line">    <span class="keyword">if</span> (mScreenAutoBrightness != newScreenAutoBrightness) &#123;</span><br><span class="line">        <span class="keyword">if</span> (DEBUG) &#123;</span><br><span class="line">            Slog.d(TAG, <span class="string">&quot;updateAutoBrightness: mScreenAutoBrightness=&quot;</span></span><br><span class="line">                    + mScreenAutoBrightness + <span class="string">&quot;, newScreenAutoBrightness=&quot;</span></span><br><span class="line">                    + newScreenAutoBrightness);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        mScreenAutoBrightness = newScreenAutoBrightness;</span><br><span class="line">        mLastScreenAutoBrightnessGamma = gamma;</span><br><span class="line">        <span class="keyword">if</span> (sendUpdate) &#123;</span><br><span class="line">            mCallbacks.updateBrightness();<span class="comment">//回调到DisplayPowerController中更新亮度。</span></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>这是的自动背光的主要算法，这个时候可能对<code>mScreenAutoBrightnessSpline.interpolate(mAmbientLux)</code>这个方法觉得很没有头脑，我这里就不po源码了，太多了，还是一个算法，感兴趣朋友可以去看下，我本人不喜欢算法就不多研究了，大致就是从两个端点上取出一个直线来获取对应输入端的输出。我们现不做展开，后面再说这个。</p><p>通过上面代码的可以发现，与最终背光亮度有关的其实有两个变量，一个是从spline中算出的值，一个是gamma值。那我们来剖析下。</p><h4 id="从spline中算出的值"><a href="#从spline中算出的值" class="headerlink" title="从spline中算出的值"></a>从spline中算出的值</h4><p><code>mScreenAutoBrightnessSpline.interpolate(mAmbientLux)</code>这个方法是干什么呢？首先它的入参是环境光变量，是直接从环境光传感器中读出来的数据，单位时lux或者nit。值的范围一般大约是0-5000。一般我们正常的工作写字等这种书写照明环境一般在350左右。那么这个spline是啥？spline相当于是个数组，创建的时候就用配置写好的曲线值来匹配。看下它的创建方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Spline <span class="title function_">createAutoBrightnessSpline</span><span class="params">(<span class="type">int</span>[] lux, <span class="type">int</span>[] brightness)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (lux == <span class="literal">null</span> || lux.length == <span class="number">0</span> || brightness == <span class="literal">null</span> || brightness.length == <span class="number">0</span>) &#123;</span><br><span class="line">        Slog.e(TAG, <span class="string">&quot;Could not create auto-brightness spline.&quot;</span>);</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">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> brightness.length;</span><br><span class="line">        <span class="type">float</span>[] x = <span class="keyword">new</span> <span class="title class_">float</span>[n];</span><br><span class="line">        <span class="type">float</span>[] y = <span class="keyword">new</span> <span class="title class_">float</span>[n];</span><br><span class="line">        y[<span class="number">0</span>] = normalizeAbsoluteBrightness(brightness[<span class="number">0</span>]);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; n; i++) &#123;</span><br><span class="line">            x[i] = lux[i - <span class="number">1</span>];</span><br><span class="line">            y[i] = normalizeAbsoluteBrightness(brightness[i]);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">Spline</span> <span class="variable">spline</span> <span class="operator">=</span> Spline.createSpline(x, y);</span><br><span class="line">        <span class="keyword">if</span> (DEBUG) &#123;</span><br><span class="line">            Slog.d(TAG, <span class="string">&quot;Auto-brightness spline: &quot;</span> + spline);</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">float</span> <span class="variable">v</span> <span class="operator">=</span> <span class="number">1f</span>; v &lt; lux[lux.length - <span class="number">1</span>] * <span class="number">1.25f</span>; v *= <span class="number">1.25f</span>) &#123;</span><br><span class="line">                Slog.d(TAG, String.format(<span class="string">&quot;  %7.1f: %7.1f&quot;</span>, v, spline.interpolate(v)));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> spline;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IllegalArgumentException ex) &#123;</span><br><span class="line">        Slog.e(TAG, <span class="string">&quot;Could not create auto-brightness spline.&quot;</span>, ex);</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><p>可以看到这个就是这个spline的创建过程，但是我们更关心两个入参，入参决定了这个环境光0-5000和背光亮度0-255之间的对应关系，这个方法是被以下方法所调用的：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">createAutoBrightnessSpline</span><span class="params">()</span> &#123;</span><br><span class="line">    screen_auto_brightness_adj = Settings.System.getFloat(mContext.getContentResolver(), Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, -<span class="number">1</span>);</span><br><span class="line">    <span class="type">float</span> <span class="variable">temp</span> <span class="operator">=</span> Math.round(screen_auto_brightness_adj);</span><br><span class="line">    <span class="type">int</span>[] lux;</span><br><span class="line">    <span class="type">int</span>[] screenBrightness;</span><br><span class="line">    lux = resources.getIntArray(</span><br><span class="line">            com.android.internal.R.array.config_autoBrightnessLevels);</span><br><span class="line">    screenBrightness = resources.getIntArray(</span><br><span class="line">            com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);</span><br><span class="line">    mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过上面的方法其实就可以指导，是从xml配置文件中拿到这个对应关系的。那我们看下这个xml：</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">integer-array</span> <span class="attr">name</span>=<span class="string">&quot;config_autoBrightnessLevels&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>50<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>300<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>400<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>600<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>800<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>1000<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>1300<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>1600<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>2000<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>3000<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>4000<span class="tag">&lt;/<span class="name">item</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">integer-array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">integer-array</span> <span class="attr">name</span>=<span class="string">&quot;config_autoBrightnessLcdBacklightValues&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>20<span class="tag">&lt;/<span class="name">item</span>&gt;</span>   <span class="comment">&lt;!-- 0-50 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>380<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 50-300 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>400<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 300-400 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>475<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 400-600 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>580<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 600-800 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>650<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 800-1000 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>750<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 1000-1300 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>820<span class="tag">&lt;/<span class="name">item</span>&gt;</span>  <span class="comment">&lt;!-- 1300-1600 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>1100<span class="tag">&lt;/<span class="name">item</span>&gt;</span> <span class="comment">&lt;!-- 1600-2000 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>1450<span class="tag">&lt;/<span class="name">item</span>&gt;</span> <span class="comment">&lt;!-- 2000-3000 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>1700<span class="tag">&lt;/<span class="name">item</span>&gt;</span> <span class="comment">&lt;!-- 3000-4000 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">item</span>&gt;</span>2047<span class="tag">&lt;/<span class="name">item</span>&gt;</span> <span class="comment">&lt;!-- 4000+ --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">integer-array</span>&gt;</span></span><br></pre></td></tr></table></figure><p>通过这一步步的代码追踪，现在就可以明确的知道mScreenAutoBrightnessSpline.interpolate(mAmbientLux)是从这个xml中获取了对应关系，然后再通过生成算法，拼接除了一个曲线，然后在通过算法，获取出的一个特定的值。</p><h4 id="gamma"><a href="#gamma" class="headerlink" title="gamma"></a>gamma</h4><p>这个gamma的调整算法在Android 8.0后去掉了<br>这个gamma值我们看到是通过<code>MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma,Math.min(1.0f, Math.max(-1.0f,-mScreenAutoBrightnessAdjustment)))</code>这种指数运算得来的，而自动亮度开关打开时，进度条的调整值时-1到1，进度条的一半50%的位置mScreenAutoBrightnessAdjustment这个值是0.而最终的gamma值是受到这个adjGamma的影响gamma *&#x3D; adjGamma;&#x2F;&#x2F;计算gamma值而adjGamma又是通过这个mScreenAutoBrightnessAdjustmentMaxGamma的指数运算得来的，mScreenAutoBrightnessAdjustmentMaxGamma的值是在<code>frameworks/base/core/res/res/values/config.xml</code>中定义：</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></pre></td><td class="code"><pre><span class="line">&lt;!-- The maximum range of gamma adjustment possible using the screen</span><br><span class="line">         auto-brightness adjustment setting. --&gt;</span><br><span class="line">    &lt;fraction name=&quot;config_autoBrightnessAdjustmentMaxGamma&quot;&gt;300%&lt;/fraction&gt;</span><br></pre></td></tr></table></figure><p>我们看到原生是3意味着变化是在1&#x2F;3次方到3次方之间，这个趋势是符合人眼在低光线亮度时对光线变化相对比较敏感的处理。<br>这里gamma值的调整变化时完全根据进度度条来的，同时通过指数运算放大，然后最后作用于亮度上。</p><h2 id="如何调整背光曲线"><a href="#如何调整背光曲线" class="headerlink" title="如何调整背光曲线"></a>如何调整背光曲线</h2><p>这里同样我们也是按照手动和自动两个部分来说。</p><p><img src="https://i.loli.net/2020/10/27/DTgSO5mzafjdGpF.png" alt="dev.png"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><h3 id="手动背光调节思路"><a href="#手动背光调节思路" class="headerlink" title="手动背光调节思路"></a>手动背光调节思路</h3><p>上面我们调查源码这部分的时候看到，源码是有个回调机制，因此导致了进度条调节的背光曲线为线性变化，那我们现在需要让这个背光曲线变成指数型曲线或者其他任何自定义的曲线怎么处理呢？</p><h3 id="算法拟合曲线"><a href="#算法拟合曲线" class="headerlink" title="算法拟合曲线"></a>算法拟合曲线</h3><p>其实很简单，就是把最终设置到settings.db数据库中的值给定死，那么屏幕亮度就会按照我们的想法变化。有了这个目标我们就可以找到设置亮度的地方前增加一个算法就可以了，用这个算法来调整原先线性的曲线变为我们想要的曲线。这个算法的公式可以通过客户给出的具体要求来做曲线拟合。</p><p>至于如何才能拟合曲线，这里给个方法，当然数学好的同学肯定自有高招，我这里只是给个还算比较方便的方法，就是用excel，先在表格中用给定的值记录进去做出表格，然后先做出目标曲线（这里要注意的是，图标做的时候用的不是折线图，而是应该选带平滑线的散点图），然后在图表中添加趋势线，在编辑中把几种类型都选下看看那种更接近目标。</p><h3 id="修复进度条问题"><a href="#修复进度条问题" class="headerlink" title="修复进度条问题"></a>修复进度条问题</h3><p>但是算法有了就万事大吉了吗？并不是！源码的时候发现改写后的背光亮度设置进去后，会出发回调重新设置进度条的最大值和当前进度，这个事情就比较麻烦了。首先我们会发现我们增加的算法会影响进度条的调整，在我尝试改动的时候就发现以下的问题:</p><ol><li>在手动设置了亮度后发现进度标识自动跳走的</li><li>进度条百分比显示不再线性</li><li>无法自由滑动进度条</li><li>进度条只能在特定点停下，设置到别的点会自动跳到附近的点</li><li>进度条百分比不再完整</li></ol><p>这些问题的出现弄得我一度崩溃，但是还是要慢慢来，首先我们知道了时因为回调机制的原因导致进度条怎么都不能符合我们的要求，那么我们先把它去掉，不再回调。这个时候发现,诶？进度条似乎不再能到255了，只有0-100了，而之前的百分比似乎也不能调到96%以后，就是因为回调设置进去的进度条的值是配置文件中亮度最大值和亮度最小值的差值，并且显示的百分比又是在这个范围内计算的比例。知道了这个我们只要预先直接给进度条设置好总长值，然后回调的时候让其根据我们增加的曲线算法反向计算一次得出目前的屏幕亮度对应0-255的值时多少就好了。这里可能有点绕，我强调下，***我们不仅需要能通过进度条得到对应设置0-255的亮度值，还要能根据这个亮度值反向计算得到对应于进度条上0-255的比例值，然后设置到进度条上，这样才能正确显示进度条。***回调会从settings数据库中得到当前设置的背光亮度值，然后反算完后就得到对应目前进度条上的亮度，再回设置就<br>这样处理后时的确可以解决进度条显示的问题，但是百分比的显示是在settings里面的，并且不止一个地方有监听调用，这样的话真的要把这个东西改成线性就太麻烦了，观察市面上的大部分手机后，<em><strong>建议直接去除亮度百分比的显示</strong></em>，其实只要目标曲线不是线性的，那么我们的百分比也就不会是线性的。</p><p><img src="https://i.loli.net/2020/10/27/Uu1iZRAKCg5xmJL.png" alt="原手动背光调节.png"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><h3 id="其他问题"><a href="#其他问题" class="headerlink" title="其他问题"></a>其他问题</h3><p>除了刚才说的进度条问题我在调试的过程中还发现了几个问题。</p><div class="trm-note tip"><div class="trm-note-title">推出设置中的显示界面，进度条位置就不记录了</div><p>A:这个主要是回调那部分没有改好。</p></div><div class="trm-note tip"><div class="trm-note-title">恢复出厂后手动进度的位置不在原来的50%的位置。</div><p>A:这个问题主要是原先时读取的默认102在</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;integernameintegername=&quot;def_screen_brightness&quot;&gt;102<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br></pre></td></tr></table></figure><p>但是这个值对应的现在曲线的百分比就不是在50%而是应该按照新算法计算，所以需要改成自己的值。</p></div><h3 id="自动背光调节思路"><a href="#自动背光调节思路" class="headerlink" title="自动背光调节思路"></a>自动背光调节思路</h3><p>源码提供了<code>frameworks/base/core/res/res/values/config.xml</code>这个配置中直接更改节点值的方式来修改自动背光曲线，这种方式我们这里不做过多的讨论了。下面来说说自定义的背光曲线怎么弄。</p><p><img src="https://i.loli.net/2020/10/27/f3qMLmUe54u72pA.png" alt="原自动背光调节1.png"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><h3 id="测定0-255亮度值对应屏幕nit亮度"><a href="#测定0-255亮度值对应屏幕nit亮度" class="headerlink" title="测定0-255亮度值对应屏幕nit亮度"></a>测定0-255亮度值对应屏幕nit亮度</h3><p>这个如标题，至于为啥要这么做，主要是在屏幕供应商确定，驱动确定的基础上，这个0-255的亮度值与屏幕nit亮度之间的关系就基本时确定的了。测量出了这个关系，对后面应用亮度时可以提高不少效率。</p><h3 id="通过算法来匹配自动背光曲线"><a href="#通过算法来匹配自动背光曲线" class="headerlink" title="通过算法来匹配自动背光曲线"></a>通过算法来匹配自动背光曲线</h3><p>我们在匹配的时候可能会遇到需要适配各种不同的情况，比如限定了进度条各位置的背光曲线，这样的话，等于我们有两个输入变量，第一个时环境光，第二个时进度条的位置带来的亮度调整。<br>首先，我们通过算法满足环境光变化时，背光亮度的的曲线。这里要重点说的是，如果定制曲线不仅仅只规定了一条，而是连调节变化范围的区间也规定了的话，应该考虑直接替换原码的gamma的计算，毕竟源码是通过定了一条背光曲线后，通过255阶的gamma来调整基于设定配置好的背光曲线的进度条最小或最高的调整。大约是从1&#x2F;3到3次方的一个变化，因此如果需要定制则建议直接替换自己的定制算法，并且在Android8.0以后已经去掉gamma值的仅算调整这个算法，这里就提一句，主要是替换算法的时候可不要有什么心里负担。</p><h3 id="自动背光曲线调整进度条不平滑"><a href="#自动背光曲线调整进度条不平滑" class="headerlink" title="自动背光曲线调整进度条不平滑"></a>自动背光曲线调整进度条不平滑</h3><p>在这里要重点讲下这个问题，由于我们考虑的时客户给定了变化范围的定制曲线，我们利用算法框住这个范围的时候会发现，原生算法中进度条的变化范围时-1到1的。我们自己处理这个曲线的时候是很难用出面积来表示的，当然数学好的有办法的除外。我这里就说下，把-1到1这个范围取整后利用曲线取出的数值后怎么办？<br>当我们完成曲线后，会发现由于取整，比如我们有三条曲线，我们会在进度条左右1&#x2F;3的点时发生亮度突变（当然一边左1&#x2F;3点亮度相对暗，突变看起来特别明显），这个突变时由于我们的计算结果因取整从相邻的曲线跃迁到目标曲线的一个结果。我们可以通过在增加一个调整算法来将类似的变化调整为线性的。<br>增加的算法直接划出的环境光区间内的计算最低值和最高值的线性条件（这个是斜线）也可以计算区间内环境光最低和最好的两条线性，再以线性条件的平均值来作为区间变化的线性条件，并在总环境光区间内添加多条这样的线性约束后，整体的变化就会类似与面积处理的结果，肉眼就难以看出了。同时我们指导环境光的变化一般时固定的，也不会就算有变化，也没有拖拉进度条这么快，因此每次经过这两次的计算，得到的屏幕亮度基本就可以符合定制要求了。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过上面的描述和算法对屏幕亮度的调整等措施，我们确实可以在系统的设置中做到对背光曲线的调整，但从最终角度讲不应该是这么来处理的。应该是由部件和厂家来测量出需要的值，让软件端将调整好的曲线写入配置文件中才是一个比较妥善的方式。</p>]]>
    </content>
    <id>https://ainavigation.org/2019/02/13/Android%20LCD%E5%B1%8F%E5%B9%95%E4%BA%AE%E5%BA%A6%E6%9B%B2%E7%BA%BF%E8%B0%83%E6%95%B4/</id>
    <link href="https://ainavigation.org/2019/02/13/Android%20LCD%E5%B1%8F%E5%B9%95%E4%BA%AE%E5%BA%A6%E6%9B%B2%E7%BA%BF%E8%B0%83%E6%95%B4/"/>
    <published>2019-02-13T04:13:00.000Z</published>
    <summary>
      <![CDATA[<p>由于公司项目比较急，我也是刚开始接触源码上修改代码，但是正因如此这次的需求来了之后对Android屏幕亮度曲线调节有了一个新的认知，记录下来也算时一种经验分享。我会从自动和手动两个方面来说明，最后在补充一些使用的测试仪器时候的一些心得吧。</p>
<h2 id="对Andr]]>
    </summary>
    <title>Android LCD屏幕亮度曲线调整</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Linux" scheme="https://ainavigation.org/categories/Linux/"/>
    <category term="archlinux" scheme="https://ainavigation.org/tags/archlinux/"/>
    <category term="mobile system" scheme="https://ainavigation.org/tags/mobile-system/"/>
    <content>
      <![CDATA[<p>在U盘上安装ArchLinux系统<br>其实在U盘上安装Linux系统基本都能装,也有针对U盘设计的系统像puppy之类的,选Arch主要是个人喜好,还有就是它的安装是手动的,不仅能学到不少linux的知识,也能更好的去适配不同的电脑,arch的可定制化程度非常高,所以更加的灵活也能适应更多的人去使用</p><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><h3 id="U盘"><a href="#U盘" class="headerlink" title="U盘"></a>U盘</h3><p>要装系统U盘不能太差,我要求比较高,U盘建议16G以上,高速读写不用说最好100m&#x2F;s以上,4K性能不能忽略(别问我为什么知道,到时候你发现几百大洋买回的U盘用的很不爽别来找我),SanDisk至尊系列亲测还行,如果你们能买到OWC的envy Pro mini最好,这是我到目前为止看的觉得最好的,但是我没有用过,4k性能不知道</p><h3 id="ArchLinux安装镜像"><a href="#ArchLinux安装镜像" class="headerlink" title="ArchLinux安装镜像"></a>ArchLinux安装镜像</h3><p>ArchLinux官方网站<br>自己下载最新的镜像,以下步骤为2016年1月尝试实践记录,请参考wikiArchLinux官方安装指导自行对比</p><p>ArchLinux的wiki非常丰富,要养成自行查找,解决问题的好习惯(说给自己听的)</p><h3 id="找个安装环境——VMware"><a href="#找个安装环境——VMware" class="headerlink" title="找个安装环境——VMware"></a>找个安装环境——VMware</h3><p>安装到U盘的确可以直接用实体机启动,但是做live盘要多用一个U盘或者cd,还有很大的可能会不小心格错盘,导致追悔莫及,所以利用虚拟机安装方便快捷,VMware的安装教程自行寻找,用VirtualBox的也可以参考</p><h2 id="安装ArchLinux"><a href="#安装ArchLinux" class="headerlink" title="安装ArchLinux"></a>安装ArchLinux</h2><p>新建虚拟机,然后选择ArchLinux安装镜像启动,插上U盘,将U盘挂载到虚拟机中</p><h3 id="分区"><a href="#分区" class="headerlink" title="分区"></a>分区</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lsblk</span><br></pre></td></tr></table></figure><p>查看U盘是否挂载上去,不出意外的话,应该会是出现在<code>/dev/sdb</code>检查容量大小是否一致(以下就认为U盘挂载在<code>/dev/sdb</code>)</p><p><a href="https://tldp.org/HOWTO/Partition/fdisk_partitioning.html">fdisk</a> — Linux 自带的命令行分区工具</p><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></pre></td><td class="code"><pre><span class="line">fdisk /dev/sdb</span><br><span class="line"><span class="comment">#建立boot区</span></span><br><span class="line"></span><br><span class="line">–&gt;n p 4096 +512M</span><br><span class="line"></span><br><span class="line"><span class="comment">#建立根目录/区</span></span><br><span class="line"></span><br><span class="line">–&gt; n p enter enter</span><br><span class="line"></span><br><span class="line"><span class="comment">#查看分区表</span></span><br><span class="line"></span><br><span class="line">–&gt; p</span><br><span class="line"></span><br><span class="line"><span class="comment">#将分区表写入U盘</span></span><br><span class="line"></span><br><span class="line">–&gt; w</span><br></pre></td></tr></table></figure><p>cfdisk — 使用 ncurses 库编写的具有伪图形界面的命令行分区工具<br><a href="https://www.kernel.org/">https://www.kernel.org/</a><br>警告: cfdisk 创建的第一个分区起始位置是63扇区而不是通常的2048扇区这将会在 SSD 和使用高级格式化(4k 扇区)的设备上造成性能问题<br>GRUB2 也会受影响GRUB legacy 和 Syslinux不受影响<br>gdisk — GPT 版的 fdisk<br><a href="http://www.rodsbooks.com/gdisk/">http://www.rodsbooks.com/gdisk/</a><br>cgdisk — GPT 版的 cfdisk<br><a href="http://www.rodsbooks.com/gdisk/">http://www.rodsbooks.com/gdisk/</a><br>sgdisk — Scriptable version of gdisk.<br><a href="http://www.rodsbooks.com/gdisk/sgdisk-walkthrough.html">http://www.rodsbooks.com/gdisk/sgdisk-walkthrough.html</a><br>GNU Parted — 命令行分区工具<br><a href="http://www.gnu.org/software/parted/parted.html">http://www.gnu.org/software/parted/parted.html</a><br>GParted — GTK 图形界面的分区工具<br><a href="http://gparted.sourceforge.net/">http://gparted.sourceforge.net/</a><br>Partitionmanager — QT 图形界面的分区工具<br><a href="http://sourceforge.net/projects/partitionman/">http://sourceforge.net/projects/partitionman/</a><br>QtParted — 与 Partitionmanager 类似,在 AUR 中可获取<br><a href="http://qtparted.sourceforge.net/">http://qtparted.sourceforge.net/</a></p><h3 id="格式化U盘建立文件系统"><a href="#格式化U盘建立文件系统" class="headerlink" title="格式化U盘建立文件系统"></a>格式化U盘建立文件系统</h3><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></pre></td><td class="code"><pre><span class="line">mkfs.ext4 /dev/sdb1</span><br><span class="line"><span class="comment"># 以ext4格式,格式化sdb1</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">mkfs.ext4 /dev/sdb2</span><br><span class="line"><span class="comment"># 以ext4格式,格式化sdb2</span></span><br></pre></td></tr></table></figure><h3 id="联网"><a href="#联网" class="headerlink" title="联网"></a>联网</h3><ol><li>首先ping baidu.com如果能ping通说明已经联网了,就跳过这一部分</li><li>ping 8.8.8.8或者ping 114.114.114.114能ping通说明Dns解析服务器有问题编辑这个文件<code>/etc/resolv.conf</code></li><li>以上两步都不行的话,先查服务</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl --<span class="built_in">type</span>=service</span><br></pre></td></tr></table></figure><p>看看有没有fail的项,如果有就需要重启这项服务<br>比如</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl stop dhcpcd@enp2s0.service</span><br></pre></td></tr></table></figure><p>先停止服务再重启</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl restart dhcpcd@enp2s0.service</span><br></pre></td></tr></table></figure><p>这里失败是会有日志的,你可以<br>4. 一般的话这样就可以了,如果还不行,请换网络配置(例如虚拟机桥接网络的,改成NAT),一般虚拟机模拟出来的网卡之类的,不会有这些问题</p><h3 id="驱动"><a href="#驱动" class="headerlink" title="驱动"></a>驱动</h3><p>接下来说说驱动的事,ArchLinux开机会启动udev来检测驱动等,并在启动时自动载入必要的模块</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lspci</span><br></pre></td></tr></table></figure><p>可以查看所有pci借口上的硬件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lspci |grep Ethernet</span><br></pre></td></tr></table></figure><p>会输出类似</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">02:00.0 Ethernet controller: Attansic Technology Corp. L1 Gigabit Ethernet Adapter (rev b0)</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lspci -s 02:00.0 -v</span><br></pre></td></tr></table></figure><p>查看02:00.0总线上的详细信息</p><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></pre></td><td class="code"><pre><span class="line">02:00.0 Ethernet controller: Attansic Technology Corp. L1 Gigabit Ethernet Adapter (rev b0)</span><br><span class="line">…</span><br><span class="line">Kernel driver <span class="keyword">in</span> use: atl1</span><br><span class="line">Kernel modules: atl1</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dmesg | grep module_name</span><br></pre></td></tr></table></figure><p>会告诉你是否驱动加载成功</p><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></pre></td><td class="code"><pre><span class="line">$ dmesg |grep atl1</span><br><span class="line">…</span><br><span class="line">atl1 0000:02:00.0: eth0 <span class="built_in">link</span> is up 100 Mbps full duplex</span><br></pre></td></tr></table></figure><p>没有驱动当然要装驱动啦,你问我怎么装？我很严肃的告诉你！要联网！这就是为啥用虚拟机装的事了,虚拟机一般虚拟出来的硬件,在ArchLinux的基本驱动都能通用,你要是你的电脑的网卡正好是Atheros AR8161 Gigabit和我一样,恭喜你,你联网一会儿就会断,根本不能好好玩耍,我到现在都没能整明白为啥,但是wiki上这个网卡有收录,告诉你要加载alx模块,可是我编译不通过,这个以后解决这个问题,暂时先跳过</p><h4 id="网络部分的一些小命令"><a href="#网络部分的一些小命令" class="headerlink" title="网络部分的一些小命令"></a>网络部分的一些小命令</h4><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></pre></td><td class="code"><pre><span class="line">ip addr 查看现有网络连接及ip地址(这里查出来的会有个自动命名的名字比如enp2s0下面的***就应该用这个名字代替)</span><br><span class="line">ip <span class="built_in">link</span> 查看现有网络连接</span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> *** up 设置***连接网络</span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> *** down 设置***断网</span><br><span class="line">dhcpcd *** 使***启动dhcp服务</span><br><span class="line"><span class="built_in">ls</span> /sys/class/net 查看现有网络接口名字</span><br><span class="line">dhcpcd -k 释放dhcp获得的IP地址</span><br><span class="line">systemctl status dhcpcd@enp2s0.service 查看enp2s0的dhcp服务状态</span><br><span class="line">systemctl --<span class="built_in">type</span>=service 查看系统已启动的服务</span><br></pre></td></tr></table></figure><p>目前就学了这些,欢迎补充</p><h3 id="挂载分区"><a href="#挂载分区" class="headerlink" title="挂载分区"></a>挂载分区</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mount /dev/sdb2 /mnt</span><br></pre></td></tr></table></figure><p>(挂载sdb2 到 &#x2F;mnt)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p /mnt/boot</span><br></pre></td></tr></table></figure><p>(在mnt目录下创建boot目录)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mount /dev/sdb1 /mnt/boot</span><br></pre></td></tr></table></figure><p>(挂载sdb1 到 <code>/mnt/boot</code>)</p><p>如果有建立了<code>/home</code>等目录的同理</p><p>如果想要用swap 请参考<a href="https://wiki.archlinux.org/index.php/Swap_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)">wiki–swap</a></p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>在确认联网的情况下,就可以安装了,不过在真正安装前,选择快速的镜像源很重要,国内163和清华的比较快</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano /etc/pacman.d/mirrorlist</span><br></pre></td></tr></table></figure><p>按ctrl+K删除整行,去掉别的,就留163的和清华的即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacstrap -i /mnt base base-devel</span><br></pre></td></tr></table></figure><p>安装系统,默认全装</p><p><code>–&gt;enter –&gt;enter –&gt;Y</code></p><p>等待安装完成</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">genfstab -U -p /mnt &gt;&gt; /mnt/etc/fstab</span><br></pre></td></tr></table></figure><p>生成fstab</p><p>设置新系统</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arch-chroot /mnt</span><br></pre></td></tr></table></figure><p>转入新系统中,这样就在我们新安装好的系统中进行操作了,可以看到<code>root@archiso</code>变为<code>sh4.3</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano /etc/locale.gen</span><br></pre></td></tr></table></figure><p>选择文字编码,找到en_US.UTF-8 UTF-8和zh开头的都打开注释</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">locale-gen</span><br></pre></td></tr></table></figure><p>更新文字编码系统<br>在 <code>/etc/locale.conf</code> 里设置系统locale偏好；单个用户请设置<code>$HOME/.config/locale.conf</code>:</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># echo LANG=zh_CN.UTF8 &gt; /etc/locale.conf</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">ln</span> -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</span><br></pre></td></tr></table></figure><p>对于国内的基本可以用上海时间</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hwclock --systohc</span><br></pre></td></tr></table></figure><p>应该可以同步时间</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> **** &gt; /etc/hostname</span><br></pre></td></tr></table></figure><p>设置主机名****</p><h3 id="设置U盘启动"><a href="#设置U盘启动" class="headerlink" title="设置U盘启动"></a>设置U盘启动</h3><p>这个很重要,为了启动后能首先加载usb</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano /etc/mkinitcpio.conf</span><br></pre></td></tr></table></figure><p>在udev 后面加入usb</p><p style="border-radius:4px; border:1px; margin:20px 2px; background-color:#65909090; padding:12px 20px; color: #F8F8FF;">HOOKS=”base udev usb autodetect modconf block filesystems keyboard fsck”</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkinitcpio -p linux</span><br></pre></td></tr></table></figure><p>重新初始化内存盘</p><h3 id="安装引导程序"><a href="#安装引导程序" class="headerlink" title="安装引导程序"></a>安装引导程序</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S syslinux gptfdisk</span><br></pre></td></tr></table></figure><h4 id="安装syslinux"><a href="#安装syslinux" class="headerlink" title="安装syslinux"></a>安装syslinux</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">syslinux-install_update –iam</span><br></pre></td></tr></table></figure><h4 id="安装引导项"><a href="#安装引导项" class="headerlink" title="安装引导项"></a>安装引导项</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">blkid</span><br></pre></td></tr></table></figure><p>查看UUID,记录sdb2的UUID 目的是为了用UUID 做标识来启动操作系统,否则换了电脑硬盘标签变化就不能启动了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano /boot/syslinux/syslinux.cfg</span><br></pre></td></tr></table></figure><p>修改</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># UI menu.c32 注释掉</span></span><br><span class="line">UI vesamenu.c32 取消注释</span><br></pre></td></tr></table></figure><p>把LABEL arch下面的root改成</p><p style="border-radius:4px; border:1px; margin:20px 2px; background-color:#65909090; padding:12px 20px; color: #F8F8FF;">root=UUID="****************************" rw</p><p>完成了这步,在你开机选这个U盘启动后应该就能出现syslinux的引导界面了,如果不行,请自行wiki换grub之类的</p><h3 id="新建用户"><a href="#新建用户" class="headerlink" title="新建用户"></a>新建用户</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd </span><br></pre></td></tr></table></figure><p>先设置root用户的密码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S zsh</span><br></pre></td></tr></table></figure><h3 id="安装zsh"><a href="#安装zsh" class="headerlink" title="安装zsh"></a>安装zsh</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">useradd -m -g <span class="built_in">users</span> -G wheel -s /bin/zsh ****</span><br></pre></td></tr></table></figure><p>创建一个名为****的用户,并使用zsh作默认shell</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd ****</span><br></pre></td></tr></table></figure><p>为****设置密码</p><h3 id="添加root权限"><a href="#添加root权限" class="headerlink" title="添加root权限"></a>添加root权限</h3><p>如果后面使用的时候出现不能sudo,提示is not in the sudoers file. This incident will be reported.<br>以root权限编辑 <code>/etc/sudoers</code>文件,在 <code>root ALL=(ALL) ALL</code>下面 加上 <code>[username] ALL=(ALL) ALL</code></p><p>也可以去掉<code>#%wheel ALL=(ALL) ALL</code>这一行前面的#</p><h3 id="更新系统"><a href="#更新系统" class="headerlink" title="更新系统"></a>更新系统</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman –Syu</span><br></pre></td></tr></table></figure><p>更新系统</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman-db-upgrade</span><br></pre></td></tr></table></figure><p>更新数据库</p><p>基本配置就结束了,下面是锦上添花</p><h3 id="配置WIFI"><a href="#配置WIFI" class="headerlink" title="配置WIFI"></a>配置WIFI</h3><p>把无线也写在这里吧,虚拟机中安装一般只会是有线状态,出问题也基本就是dhcp没获取到ip,手动重启一遍dhcp就会好</p><p>首先应该安装网卡驱动,ArchLinux在安装base-devel的时候一般都带着了,除非你的没有,那就要查一下资料自己装下了</p><p>首先需要安装无线相关的软件</p><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></pre></td><td class="code"><pre><span class="line">pacman -S iw wpa_supplicant wireless_tools dialog networkmanager network-manager-applet</span><br><span class="line"></span><br><span class="line">systemctl <span class="built_in">enable</span> NetworkManager</span><br></pre></td></tr></table></figure><p>这样就能开机自动启动网络配置。</p><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></pre></td><td class="code"><pre><span class="line">pacman -S alsa-utils</span><br></pre></td></tr></table></figure><p><em>官方说法叫解除静音</em></p><h3 id="安装桌面系统"><a href="#安装桌面系统" class="headerlink" title="安装桌面系统"></a>安装桌面系统</h3><p>请自行根据喜欢的桌面环境去安装配置,先装X</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S xorg xorg-xinit</span><br></pre></td></tr></table></figure><h4 id="安装KDE桌面环境"><a href="#安装KDE桌面环境" class="headerlink" title="安装KDE桌面环境"></a>安装KDE桌面环境</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S plasma</span><br></pre></td></tr></table></figure><p>这就能把最新的plasma5安装上,同时会找到很多依赖包,其中就包括显卡驱动,NVIDIA的显卡请选择开源的驱动,也就是默认选择,不要作死(不要问我为什么知道T_T)</p><p>安装完KDE后,如果你这时候启动X,你会发现怎么没有终端,我们熟悉的终端呢？！对！就是没有,要自己装！</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S konsole dolphin</span><br></pre></td></tr></table></figure><h4 id="安装konsole和文件管理器"><a href="#安装konsole和文件管理器" class="headerlink" title="安装konsole和文件管理器"></a>安装konsole和文件管理器</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> /etc/X11/xinit/xinitrc ~/.xinitrc</span><br></pre></td></tr></table></figure><p>拷贝X的启动文件到用户目录,针对用户修改</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nano ~/.xinitrc</span><br></pre></td></tr></table></figure><p>把exec之前的都注释掉,把exec后面的改为startkde</p><h4 id="安装GNOME桌面环境"><a href="#安装GNOME桌面环境" class="headerlink" title="安装GNOME桌面环境"></a>安装GNOME桌面环境</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S gnome gnome-extra gdm</span><br></pre></td></tr></table></figure><p>将gdm设置为开机自启动，这样开机时会自动载入桌面</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> gdm</span><br></pre></td></tr></table></figure><h4 id="美化gnome"><a href="#美化gnome" class="headerlink" title="美化gnome"></a>美化gnome</h4><p>20170826-update</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gnome-tweak-tool</span><br></pre></td></tr></table></figure><p>如果你安装了gnome-extra，那么这个工具已经被安装了，否则的话</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S gnome-tweak-tool</span><br></pre></td></tr></table></figure><h5 id="图标包"><a href="#图标包" class="headerlink" title="图标包"></a>图标包</h5><p>这里我使用的numix-circle图标包，这个图标包在aur里，直接用yaourt即可</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yaourt -S numix-circle-icon-theme-git</span><br></pre></td></tr></table></figure><p>然后在gnome-tweak-tool里启用主题</p><h5 id="主题"><a href="#主题" class="headerlink" title="主题"></a>主题</h5><p>我们可以到下面这个网站上选自己喜欢的主体，安装方式也可以根据提示步骤来</p><p><a href="https://www.gnome-look.org/">https://www.gnome-look.org/</a></p><p>然后在gnome-tweak-tool里启用</p><h5 id="gdm背景"><a href="#gdm背景" class="headerlink" title="gdm背景"></a>gdm背景</h5><p>输入以下指令</p><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></pre></td><td class="code"><pre><span class="line">curl -L -O http://archibold.io/sh/archibold</span><br><span class="line"><span class="built_in">chmod</span> +x archibold</span><br><span class="line">./archibold login-backgroung 你的背景的地址</span><br></pre></td></tr></table></figure><p>重启后gdm就会变成你要的背景</p><h5 id="gnome-shell拓展"><a href="#gnome-shell拓展" class="headerlink" title="gnome-shell拓展"></a>gnome-shell拓展</h5><p>shell拓展请进入<a href="https://extensions.gnome.org/">https://extensions.gnome.org/</a>自行按照说明安装</p><h5 id="screenfetch"><a href="#screenfetch" class="headerlink" title="screenfetch"></a>screenfetch</h5><p>screenfetch可以在终端里输出你的系统logo和状态。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S screenfetch</span><br></pre></td></tr></table></figure><p>要让screenfetch在打开终端是自动输出，在<code>~/.zshrc</code>里加入</p><p>最后<br><code>exit</code>然后<code>umount -R /mnt</code>关掉虚拟机,拔下U盘,去嗨吧</p><hr><p>20160423-update</p><h5 id="安装必要字体"><a href="#安装必要字体" class="headerlink" title="安装必要字体"></a>安装必要字体</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S ttf-dejavu wqy-zenhei wqy-microhei</span><br></pre></td></tr></table></figure><h5 id="安装火狐"><a href="#安装火狐" class="headerlink" title="安装火狐"></a>安装火狐</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S firefox</span><br></pre></td></tr></table></figure><h5 id="安装net-tools（包含ifconfig）"><a href="#安装net-tools（包含ifconfig）" class="headerlink" title="安装net-tools（包含ifconfig）"></a>安装net-tools（包含ifconfig）</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -S net-tools</span><br></pre></td></tr></table></figure><p>修改<code>/boot/syslinux/syslinux.cfg</code></p><p style="border-radius:4px; border:1px; margin:20px 2px; background-color:#65909090; padding:12px 20px; color: #F8F8FF;">timeout = 0</p><p>注销所有菜单选项</p><h5 id="启动sddm"><a href="#启动sddm" class="headerlink" title="启动sddm"></a>启动sddm</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl <span class="built_in">enable</span> sddm.service</span><br></pre></td></tr></table></figure><h5 id="安装wget，git"><a href="#安装wget，git" class="headerlink" title="安装wget，git"></a>安装wget，git</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacamn -S wget git</span><br></pre></td></tr></table></figure><h5 id="安装oh-my-zsh"><a href="#安装oh-my-zsh" class="headerlink" title="安装oh-my-zsh"></a>安装oh-my-zsh</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c <span class="string">&quot;<span class="subst">$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)</span>&quot;</span></span><br></pre></td></tr></table></figure><h5 id="安装Yaourt"><a href="#安装Yaourt" class="headerlink" title="安装Yaourt"></a>安装Yaourt</h5><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></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://aur.archlinux.org/package-query.git</span><br><span class="line"><span class="built_in">cd</span> package-query</span><br><span class="line">makepkg -si</span><br><span class="line"><span class="built_in">cd</span> ..</span><br><span class="line">git <span class="built_in">clone</span> https://aur.archlinux.org/yaourt.git</span><br><span class="line"><span class="built_in">cd</span> yaourt</span><br><span class="line">makepkg -si</span><br><span class="line"><span class="built_in">cd</span> ..</span><br></pre></td></tr></table></figure><p>更新yaourt</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yaourt -Syu --devel --aur</span><br></pre></td></tr></table></figure><h5 id="安装搜狗中文输入法"><a href="#安装搜狗中文输入法" class="headerlink" title="安装搜狗中文输入法"></a>安装搜狗中文输入法</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yaourt -S fcitx-sogoupinyin</span><br></pre></td></tr></table></figure><p>由于设置的输出是UTF-8，所以终端中不做设置可能会显示为乱码，我们设置下nano ~&#x2F;.zshrc</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> LC_ALL=en_US.UTF-8  </span><br><span class="line"><span class="built_in">export</span> LANG=en_US.UTF-8</span><br></pre></td></tr></table></figure><hr><p>20170826-update</p><h5 id="安装chrome"><a href="#安装chrome" class="headerlink" title="安装chrome"></a>安装chrome</h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yaourt -S google-chrome</span><br></pre></td></tr></table></figure><h5 id="解压软件"><a href="#解压软件" class="headerlink" title="解压软件"></a>解压软件</h5><p>需要图形化的解压软件可以这样：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S p7zip file-roller unrar</span><br></pre></td></tr></table></figure><h5 id="文件系统支持"><a href="#文件系统支持" class="headerlink" title="文件系统支持"></a>文件系统支持</h5><p>要支持制作fat文件系统，安装dosfstools，默认内核只能读取ntfs，要支持ntfs读写，安装ntfs-3g。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S ntfs-3g dosfstools</span><br></pre></td></tr></table></figure><h5 id="无线AP"><a href="#无线AP" class="headerlink" title="无线AP"></a>无线AP</h5><p>需要安装create-ap才能使用gnome3设置里的创建热点选项</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S create_ap</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ainavigation.org/2016/02/04/%E5%9C%A8U%E7%9B%98%E4%B8%8A%E5%AE%89%E8%A3%85ArchLinux%E7%B3%BB%E7%BB%9F/</id>
    <link href="https://ainavigation.org/2016/02/04/%E5%9C%A8U%E7%9B%98%E4%B8%8A%E5%AE%89%E8%A3%85ArchLinux%E7%B3%BB%E7%BB%9F/"/>
    <published>2016-02-04T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>在U盘上安装ArchLinux系统<br>其实在U盘上安装Linux系统基本都能装,也有针对U盘设计的系统像puppy之类的,选Arch主要是个人喜好,还有就是它的安装是手动的,不仅能学到不少linux的知识,也能更好的去适配不同的电脑,arch的可定制化程度非常高,所以更]]>
    </summary>
    <title>在U盘上安装ArchLinux系统</title>
    <updated>2026-04-05T02:25:26.015Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="brodcast" scheme="https://ainavigation.org/tags/brodcast/"/>
    <content>
      <![CDATA[<h2 id="broadcast静态使用"><a href="#broadcast静态使用" class="headerlink" title="broadcast静态使用"></a>broadcast静态使用</h2><h3 id="在AndroidManifest-xml中注册"><a href="#在AndroidManifest-xml中注册" class="headerlink" title="在AndroidManifest.xml中注册"></a>在AndroidManifest.xml中注册</h3><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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">receiver</span> </span></span><br><span class="line"><span class="tag">       <span class="attr">android:name</span>=<span class="string">&quot;com.example.testBR&quot;</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">action</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.test.ACTION_TEST&quot;</span> /&gt;</span></span><br><span class="line">            <span class="tag">&lt;/<span class="name">intent-filter</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;/<span class="name">receiver</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="重写onReceive方法"><a href="#重写onReceive方法" class="headerlink" title="重写onReceive方法"></a>重写onReceive方法</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceive</span><span class="params">(Context context, Intent intent)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="broadcast动态注册"><a href="#broadcast动态注册" class="headerlink" title="broadcast动态注册"></a>broadcast动态注册</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">BroadcastReceiver</span> <span class="variable">mReceiver</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BroadcastReceiver</span>()&#123;</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="keyword">void</span> <span class="title function_">onReceive</span><span class="params">(Context context, Intent intent)</span> &#123;</span><br><span class="line">        <span class="comment">// TODO Auto-generated method stub    </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="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onPause</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onPause();</span><br><span class="line">    unregisterReceiver(mReceiver);</span><br><span class="line">&#125;</span><br><span class="line"></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="keyword">void</span> <span class="title function_">onResume</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onResume();</span><br><span class="line">    <span class="type">IntentFilter</span> <span class="variable">filter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IntentFilter</span>(<span class="string">&quot;com.example.test.ACTION_TEST&quot;</span>);</span><br><span class="line">    registerReceiver(mReceiver, filter);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="发送广播"><a href="#发送广播" class="headerlink" title="发送广播"></a>发送广播</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sendBroadcast(<span class="keyword">new</span> <span class="title class_">Intent</span>(<span class="string">&quot;com.example.test.ACTION_TEST&quot;</span>));</span><br></pre></td></tr></table></figure><h3 id="Intent中附加信息"><a href="#Intent中附加信息" class="headerlink" title="Intent中附加信息"></a>Intent中附加信息</h3><h4 id="发送带有附加消息的Intent"><a href="#发送带有附加消息的Intent" class="headerlink" title="发送带有附加消息的Intent"></a>发送带有附加消息的Intent</h4><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></pre></td><td class="code"><pre><span class="line"><span class="type">Intent</span> <span class="variable">i</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Intent</span>(<span class="built_in">this</span>, TestService.class);</span><br><span class="line">     i.putExtra(name, value);</span><br><span class="line">     sendBroadcast(<span class="keyword">new</span> <span class="title class_">Intent</span>(<span class="string">&quot;com.example.test.ACTION_TEST&quot;</span>));</span><br></pre></td></tr></table></figure><h4 id="接收Intent中的附加消息"><a href="#接收Intent中的附加消息" class="headerlink" title="接收Intent中的附加消息"></a>接收Intent中的附加消息</h4><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceive</span><span class="params">(Context context, Intent intent)</span> &#123;</span><br><span class="line">intent.getIntExtra(<span class="string">&quot;action&quot;</span>, <span class="number">0</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="broadcast权限"><a href="#broadcast权限" class="headerlink" title="broadcast权限"></a>broadcast权限</h2><p>如果reeceiver声明在<code>AndroidManifest.xml</code>中，且仅限应用内部使用，则可在标签上添加<code>android:exported=&quot;false&quot;</code>属性<br>这样系统的其他应用就再也无法接触到该receiver。</p><p>另外也可以通过自己创建使用权限，在<code>AndroidManifest.xml</code>中添加标签来完成。</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">permission</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:name</span>=<span class="string">&quot;com.example.test.TestService.PRIVATE&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:protectionLevel</span>=<span class="string">&quot;signature&quot;</span></span></span><br><span class="line"><span class="tag">    &gt;</span><span class="tag">&lt;/<span class="name">permission</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.test.TestService.PRIVATE&quot;</span>/&gt;</span></span><br></pre></td></tr></table></figure><h3 id="发送带权限的broadcast"><a href="#发送带权限的broadcast" class="headerlink" title="发送带权限的broadcast"></a>发送带权限的broadcast</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">PREM_PRIVATE</span> <span class="operator">=</span> <span class="string">&quot;com.example.test.TestService.PRIVATE&quot;</span>;</span><br><span class="line">sendBroadcast(<span class="keyword">new</span> <span class="title class_">Intent</span>(<span class="string">&quot;com.example.test.ACTION_TEST&quot;</span>),PREM_PRIVATE);</span><br></pre></td></tr></table></figure><h2 id="通过ADB-shell模拟发送broadcast"><a href="#通过ADB-shell模拟发送broadcast" class="headerlink" title="通过ADB shell模拟发送broadcast"></a>通过ADB shell模拟发送broadcast</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">adb shell am broadcast &lt;option&gt; &lt;INTENT&gt;：</span><br><span class="line"></span><br><span class="line">option:</span><br><span class="line">[-a &lt;ACTION&gt;]</span><br><span class="line">[-d &lt;DATA_URI&gt;]</span><br><span class="line">[-t &lt;MIME_TYPE&gt;] </span><br><span class="line">[-c &lt;CATEGORY&gt; [-c &lt;CATEGORY&gt;] ...] </span><br><span class="line">[-e|--es &lt;EXTRA_KEY&gt; &lt;EXTRA_STRING_VALUE&gt; ...] </span><br><span class="line">[--ez &lt;EXTRA_KEY&gt; &lt;EXTRA_BOOLEAN_VALUE&gt; ...] </span><br><span class="line">[-e|--ei &lt;EXTRA_KEY&gt; &lt;EXTRA_INT_VALUE&gt; ...] </span><br><span class="line">[-n &lt;COMPONENT&gt;]</span><br><span class="line">[-f &lt;FLAGS&gt;] [&lt;URI&gt;]</span><br></pre></td></tr></table></figure><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">adb shell am broadcast -a com.android.test --es test_string &quot;this is test string&quot; --ei test_int 100 --ez test_boolean true</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ainavigation.org/2015/12/06/Android%20Broadcast%E7%9A%84%E7%AE%80%E5%8D%95%E5%BA%94%E7%94%A8/</id>
    <link href="https://ainavigation.org/2015/12/06/Android%20Broadcast%E7%9A%84%E7%AE%80%E5%8D%95%E5%BA%94%E7%94%A8/"/>
    <published>2015-12-06T02:05:56.000Z</published>
    <summary>
      <![CDATA[<h2 id="broadcast静态使用"><a href="#broadcast静态使用" class="headerlink" title="broadcast静态使用"></a>broadcast静态使用</h2><h3 id="在AndroidManifest-xml中]]>
    </summary>
    <title>Android Broadcast的简单应用</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="linux" scheme="https://ainavigation.org/tags/linux/"/>
    <category term="uid" scheme="https://ainavigation.org/tags/uid/"/>
    <category term="gid" scheme="https://ainavigation.org/tags/gid/"/>
    <category term="selinux" scheme="https://ainavigation.org/tags/selinux/"/>
    <content>
      <![CDATA[<p>在公司做项目发现&#x2F;data&#x2F;misc&#x2F;dhcp这个目录的文件无法访问，抓log后又没有显示selinux权限报错的avcdeny，但是需求又依赖于dhcp协议的解析，所以一直在寻找这个问题的解决方案。现在系统整理一下并把相关知识记录下来，Android系统来源于Linux系统所以我们先来看linux系统。</p><h2 id="linux系统中uid以及gid权限的学习"><a href="#linux系统中uid以及gid权限的学习" class="headerlink" title="linux系统中uid以及gid权限的学习"></a>linux系统中uid以及gid权限的学习</h2><p>UID：用户id，UserID，简称UID。</p><p>GID：用户组id，GroupID，简称GID。</p><p>在linux系统中如何判别一个文件的拥有者？以及分辨可以对它进行操作的用户？就是利用uid和gid。每个文件都会有所谓的拥有者id（也叫文件所有者，owner）和拥有者组id，当我们有显示文件属性需求的时候，系统会根据<code>/etc/password</code>与<code>/etc/group</code>的内容，找到uid与gid对应的账号与组名再显示出来。</p><p>在 Linux 中，一个用户 UID 标示一个给定用户。Linux系统中的用户(UID)分为3类，即普通用户、根用户、系统用户。</p><p>普通用户是指所有使用Linux系统的真实用户，这类用户可以使用用户名及密码登录系统。Linux有着极为详细的权限设置，所以一般来说普通用户只能在其家目录、系统临时目录或其他经过授权的目录中操作，以及操作属于该用户的文件。通常普通用户的UID大于500，因为在添加普通用户时，系统默认用户ID从500开始编号。<br>根用户也就是root用户，它的ID是0，也被称为超级用户，root账户拥有对系统的完全控制权：可以修改、删除任何文件，运行任何命令。所以root用户也是系统里面最具危险性的用户，root用户甚至可以在系统正常运行时删除所有文件系统，造成无法挽回的灾难。所以一般情况下，使用root用户登录系统时需要十分小心。</p><p>组(GID)又是什么呢？事实上，在Linux下每个用户都至少属于一个组。举个例子：每个学生在学校使用学号来作为标识，而每个学生又都属于某一个班级，这里的学号就相当于UID，而班级就相当于GID。当然了，每个学生可能还会同时参加一些兴趣班，而每个兴趣班也是不同的组。也就是说，每个学生至少属于一个组，也可以同时属于多个组。在Linux下也是一样的道理。</p><h3 id="linux账号与用户组权限扩展（euid，egid，fsuid，fsgid，suid，sguid）-3"><a href="#linux账号与用户组权限扩展（euid，egid，fsuid，fsgid，suid，sguid）-3" class="headerlink" title="linux账号与用户组权限扩展（euid，egid，fsuid，fsgid，suid，sguid）[^3]"></a>linux账号与用户组权限扩展（euid，egid，fsuid，fsgid，suid，sguid）[^3]</h3><p>每个进程都拥有真实的用户、组（uid、gid），有效的用户、组（euid、egid），保存的设置用户、组（suid、sgid），还有linux中专门用于文件存储存取的用户、组id（fsuid、fsgid对于unix系统没有这两个fields）。现说明进程中每种类型用户的功能：</p><ul><li><p>真实的用户、组（uid、gid）：进程的真正所有者。每当用户在shell终端登录时，都会将登录用户作为登录进程的真正所有者。通过getuid来获得进程的真正用户所有者，修改进程的真正用户所有者可以通过setuid、seteuid、setresuid、setreuid。</p></li><li><p>有效的用户、组（euid、egid）：进程的有效用户、组。进程所执行各种操作所允许的权限（process  credentials）是依据进程的有效用户来判断的，（在linux系统中（内核2.4以上）又引入了一个新的进程权限管理模型process  capabilities，通过process capabilities来确定进程所允许的各种操作[可参看《深入理解linux内核》table  20-3]）。通过geteuid来获得进程的有效用户，修改进程的有效用户可以通过setuid、seteuid、setresuid、setreuid、seteuid。</p></li><li><p>文件系统的用户、组（fsuid、fsgid）：用于进行文件访问的用户、组，这是linux系统中新引入的一类用户、组，对于unix系统文件的访问是通过euid来判断，没有函数获得进程的fsuid，用于修改有效用户的函数都会同时修改fsuid，如果要单独修改fsuid，而不修改euid，可以调用setfsuid。</p></li><li><p>保存的设置用户、组（suid、sgid）：保存的设置用户、组。进程中该类型的用户、组主要的用处是用于还原有效用户，观察到对于非超级用户用于修改有效用户的各个函数setuid、seteuid、setresuid、setreuid、seteuid普遍有一个前提条件就是如果修改后的有效用户是原先的suid则允许修改，利用这一点，进程可以修改有效用户到一个新用户，然后还原到原来的值（原来的值保存在保存设置的用户）。通过getresuid来获得进程的真实用户、有效用户、保存的设置用户。</p></li></ul><h3 id="suid和sgid详解-1"><a href="#suid和sgid详解-1" class="headerlink" title="suid和sgid详解[^1]"></a>suid和sgid详解[^1]</h3><h4 id="UNIX下关于文件权限的表示方法和解析"><a href="#UNIX下关于文件权限的表示方法和解析" class="headerlink" title="UNIX下关于文件权限的表示方法和解析"></a>UNIX下关于文件权限的表示方法和解析</h4><ul><li>SUID 是 Set User ID</li><li>SGID 是 Set Group ID</li></ul><p>UNIX下可以用<code>ls -l</code>命令来看到文件的权限。用ls命令所得到的表示法的格式是类似这样的：<code>-rwxr-xr-x</code> 。下面解析一下格式所表示的意思。这种表示方法一共有十位：</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></pre></td><td class="code"><pre><span class="line">9 8 7 6 5 4 3 2 1 0</span><br><span class="line"></span><br><span class="line">- r w x r - x r - x</span><br></pre></td></tr></table></figure><p>第9位表示文件类型,可以为p、d、l、s、c、b和-：</p><ul><li>p表示命名管道文件</li><li>d表示目录文件</li><li>l表示符号连接文件</li><li>-表示普通文件</li><li>s表示socket文件</li><li>c表示字符设备文件</li><li>b表示块设备文件</li></ul><p>第8-6位、5-3位、2-0位分别表示文件所有者的权限，同组用户的权限，其他用户的权限，其形式为rwx：</p><ul><li>r表示可读，可以读出文件的内容</li><li>w表示可写，可以修改文件的内容</li><li>x表示可执行，可运行这个程序</li></ul><p>没有权限的位置用 <code>-</code>表示</p><p>例子：</p><p><code>ls -l myfile</code>显示为：</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">-rwxr-x--- 1 foo staff 7734 Apr 05 17:07 myfile</span><br></pre></td></tr></table></figure><p>表示文件myfile是普通文件，文件的所有者是foo用户，而foo用户属于staff组，文件只有1个硬连接，长度是7734个字节，最后修改时间4月5日17:07。</p><p>所有者foo对文件有读写执行权限，staff组的成员对文件有读和执行权限，其他的用户对这个文件没有权限。</p><p>如果一个文件被设置了SUID或SGID位，会分别表现在所有者或同组用户的权限的可执行位上。例如：</p><p>1、<code>-rwsr-xr-x</code> 表示SUID和所有者权限中可执行位被设置</p><p>2、<code>-rwSr--r--</code> 表示SUID被设置，但所有者权限中可执行位没有被设置</p><p>3、<code>-rwxr-sr-x</code> 表示SGID和同组用户权限中可执行位被设置</p><p>4、<code>-rw-r-Sr--</code> 表示SGID被设置，但同组用户权限中可执行位没有被设置</p><p>其实在UNIX的实现中，文件权限用12个二进制位表示，如果该位置上的值是1，表示有相应的权限：</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></pre></td><td class="code"><pre><span class="line">11 10 9 8 7 6 5 4 3 2 1 0</span><br><span class="line"></span><br><span class="line">S  G  T r w x r w x r w x</span><br></pre></td></tr></table></figure><p>第11位为SUID位，第10位为SGID位，第9位为sticky位，第8-0位对应于上面的三组rwx位。</p><p>上面的<code>-rwsr-xr-x</code>的值为： <code>1 0 0 1 1 1 1 0 1 1 0 1</code></p><p><code>-rw-r-Sr--</code>的值为： <code>0 1 0 1 1 0 1 0 0 1 0 0</code></p><div class="trm-note tip"><div class="trm-note-title">TIP</div><p>同样的，rwx可以对应我们熟知的数值：2，4，1。<br>那么suid和sgid也是可以对应数值的（隐藏权限：SUID（4）、SGID（2）、SBIT（1）），不过是在原先的位置前，因为suid和sgid属于隐藏权限。</p></div><p>给文件加SUID和SUID的命令如下：</p><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"><span class="built_in">chmod</span> u+s  filename  <span class="comment"># 设置SUID位</span></span><br><span class="line"><span class="built_in">chmod</span> 4770 filename</span><br><span class="line"></span><br><span class="line"><span class="built_in">chmod</span> u-s filename  <span class="comment"># 去掉SUID设置</span></span><br><span class="line"><span class="built_in">chmod</span> 0770 filename</span><br><span class="line"></span><br><span class="line"><span class="built_in">chmod</span> g+s filename <span class="comment"># 设置SGID位</span></span><br><span class="line"><span class="built_in">chmod</span> 2770 filename</span><br><span class="line"></span><br><span class="line"><span class="built_in">chmod</span> g-s filename <span class="comment"># 去掉SGID设置</span></span><br><span class="line"><span class="built_in">chmod</span> 0770 filename</span><br></pre></td></tr></table></figure><h4 id="SUID和SGID的详细解析"><a href="#SUID和SGID的详细解析" class="headerlink" title="SUID和SGID的详细解析"></a>SUID和SGID的详细解析</h4><p>由于SUID和SGID是在执行程序（程序的可执行位被设置）时起作用，而可执行位只对普通文件和目录文件有意义，所以设置其他种类文件的SUID和SGID位是没有多大意义的。</p><p>首先讲普通文件的SUID和SGID的作用。例子：</p><p>如果普通文件myfile是属于foo用户的，是可执行的，现在没设SUID位，ls命令显示如下：</p><p><code>-rwxr-xr-x 1 foo staff 7734 Apr 05 17:07 myfile</code><br> 任何用户都可以执行这个程序。UNIX的内核是根据什么来确定一个进程对资源的访问权限的呢？是这个进程的运行用户的（有效）ID，包括user id和group id。用户可以用id命令来查到自己的或其他用户的user id和group id。</p><p>除了一般的user id 和group id外，还有两个称之为effective 的id，就是有效id，上面的四个id表示为：uid，gid，euid，egid。内核主要是根据euid和egid来确定进程对资源的访问权限。</p><p>一个进程如果没有SUID或SGID位，则euid&#x3D;uid egid&#x3D;gid，分别是运行这个程序的用户的uid和gid。例如kevin用户的uid和gid分别为204和202，foo用户的uid和gid为200，201，kevin运行myfile程序形成的进程的euid&#x3D;uid&#x3D;204，egid&#x3D;gid&#x3D;202，内核根据这些值来判断进程对资源访问的限制，其实就是kevin用户对资源访问的权限，和foo没关系。</p><p>如果一个程序设置了SUID，则euid和egid变成被运行的程序的所有者的uid和gid，例如kevin用户运行myfile，euid&#x3D;200，egid&#x3D;201，uid&#x3D;204，gid&#x3D;202，则这个进程具有它的属主foo的资源访问权限。</p><p>SUID的作用就是这样：让本来没有相应权限的用户运行这个程序时，可以访问他没有权限访问的资源。passwd就是一个很鲜明的例子。</p><p>SUID的优先级比SGID高，当一个可执行程序设置了SUID，则SGID会自动变成相应的egid。</p><p>下面讨论一个例子：</p><p>UNIX系统有一个&#x2F;dev&#x2F;kmem的设备文件，是一个字符设备文件，里面存储了核心程序要访问的数据，包括用户的口令。所以这个文件不能给一般的用户读写，权限设为：cr–r—– 1 root system 2, 1 May 25 1998 kmem</p><p>但ps等程序要读这个文件，而ps的权限设置如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-r-xr-sr-x 1 bin system 59346 Apr 05 1998 ps</span><br></pre></td></tr></table></figure><p>这是一个设置了SGID的程序，而ps的用户是bin，不是root，所以不能设置SUID来访问kmem，但大家注意了，bin和root都属于system组，而且ps设置了SGID，一般用户执行ps，就会获得system组用户的权限，而文件kmem的同组用户的权限是可读，所以一般用户执行ps就没问题了。但有些人说，为什么不把ps程序设置为root用户的程序，然后设置SUID位，不也行吗？这的确可以解决问题，但实际中为什么不这样做呢？因为SGID的风险比SUID小得多，所以出于系统安全的考虑，应该尽量用SGID代替SUID的程序，如果可能的话。下面来说明一下SGID对目录的影响。SUID对目录没有影响。如果一个目录设置了SGID位，那么如果任何一个用户对这个目录有写权限的话，他在这个目录所建立的文件的组都会自动转为这个目录的属主所在的组，而文件所有者不变，还是属于建立这个文件的用户。</p><h4 id="在Linux系统下suid和sgid的作用机制"><a href="#在Linux系统下suid和sgid的作用机制" class="headerlink" title="在Linux系统下suid和sgid的作用机制"></a>在Linux系统下suid和sgid的作用机制</h4><p>进程在运行的时候，有一些属性，其中包括 实际用户ID,实际组ID,有效用户ID,有效组ID等。 实际用户ID和实际组ID标识我们是谁，谁在运行这个程序,一般这2个字段在登陆时决定，在一个登陆会话期间， 这些值基本上不改变。</p><p>而有效用户ID和有效组ID则决定了进程在运行时的权限。内核在决定进程是否有文件存取权限时，是采用了进程的有效用户ID来进行判断的。</p><p>知道了这点，我们来看看SUID的解决途径：</p><p>当一个程序设置了为SUID位时，内核就知道了运行这个程序的时候，应该认为是文件的所有者在运行这个程序。即该程序运行的时候，有效用户ID是该程序的所有者，SGID也是同理。</p><h3 id="Linux系统uid和gid权限总结"><a href="#Linux系统uid和gid权限总结" class="headerlink" title="Linux系统uid和gid权限总结"></a>Linux系统uid和gid权限总结</h3><p>创建一个文件，系统会根据该用户的uid和gid来设置这个文件的owner以及gid，同时root用户具有最高权限，不受权限管理限制。在系统中我们可以根据<code>ls -l</code>显示出的文件权限以及uid和gid来确认文件对应的权限。如果当前用户并不是文件或目录的创建者在linux系统中会根据最后三位<code>rwx</code>位的设置来控制是否可以控制该文件，因此为了安全考虑一般会将文件设置为770或者775等等，可以根据文件在场景中的实际需要来设置。当然除此以外也可以让用户加入该文件所在的用户组别，即加入gid，那么即使文件访问权限是770，也可以正常以完整读写执行的权限使用该文件。如果需要让非拥有者用户可以访问该文件可以设置suid以及sgid，鉴于安全考虑建议设置sgid，可以让系统在需要访问该文件时，临时将其他用户设置为该文件拥有者的gid，因此正常访问该文件。</p><ul><li>uid相同，系统认为时所有者可以直接访问（suid位设置时，即使uid不同系统也会认为时文件所有者）</li><li>uid不同，系统认为非所有者继续确认是否属于同组<ul><li>同组，按照组权限控制访问（sgid位设置时，即使uid和gid与所有者不同系统也会认为时文件所有者所在的组，可以对应组权限访问）</li><li>不同组，则继续根据其他用户的通用权限来控制访问</li></ul></li></ul><p><em><strong>SUID仅对二进制程序可用，shell脚本不可用，对目录也不可用</strong></em></p><h2 id="Android系统的权限管理"><a href="#Android系统的权限管理" class="headerlink" title="Android系统的权限管理"></a>Android系统的权限管理</h2><h3 id="Android-系统中的UID、GID、GIDS与PID"><a href="#Android-系统中的UID、GID、GIDS与PID" class="headerlink" title="Android 系统中的UID、GID、GIDS与PID"></a>Android 系统中的UID、GID、GIDS与PID</h3><p>在 Android 上，一个用户 UID 标示一个应用程序。应用程序在安装时被分配用户 UID，应用程序在设备上的存续期间内，用户 UID 保持不变。对于普通的应用程序，GID即等于UID。</p><p>GIDS 是由框架在 Application 安装过程中生成，与 Application 申请的具体权限相关。 如果  Application 申请的相应的 permission 被 granted ，而且有对应的GIDS， 那么 这个Application 的  gids 中将 包含这个 gids。记住权限(GIDS)是关于允许或限制应用程序（而不是用户）访问设备资源。</p><p><img src="https://i.loli.net/2020/11/02/xW5mITbqjucnHEh.gif" alt="sandbox_model"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>Android  使用沙箱的概念来实现应用程序之间的分离和权限，以允许或拒绝一个应用程序访问设备的资源，比如说文件和目录、网络、传感器和  API。为此，Android 使用一些 Linux 实用工具（比如说进程级别的安全性、与应用程序相关的用户和组  ID，以及权限），来实现应用程序被允许执行的操作。</p><p>Android 应用程序运行在它们自己的 Linux 进程上，并被分配一个惟一的用户  ID。默认情况下，运行在基本沙箱进程中的应用程序没有被分配权限，因而此类应用程序访问系统或资源受到限制，Android  应用程序只能通过应用程序的 manifest 文件请求权限。</p><p>不同的应用程序可以运行在相同的进程中。对于此方法，首先必须使用相同的私钥签署这些应用程序，然后必须使用 manifest 文件给它们分配相同的  Linux 用户 ID，这通过用相同的值&#x2F;名定义 manifest 属性 <code>android:sharedUserId</code>  来做到，从而共享对其数据和代码的访问。</p><p><img src="https://i.loli.net/2020/11/02/yZOkHGCpRKzc6Ld.gif" alt="same UID"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>基于用户id的安全机制，其实使用的是标准的Linux的权限控制的机制，本用户、本组的和其他用户各自有读、写、执行3中权限。系统在这方面的控制主要有：</p><div class="trm-note tip"><div class="trm-note-title">TIP</div><p>文件系统的各个文件具有Uid和Gid，并指定权限。<br>每个进行具有自己的Uid和Gid，并指定它属于哪些组。<br>每个进程可以根据本用户规则访问其Uid可以访问的文件。<br>每个进程可以根据组规则访问其所属的所有组(Groups)可以访问的文件。<br>如果文件定义了其他的用户可以访问的权限，可以被任何任何程序访问。<br>任何进程都不可以访问不具有权限的文件。</p></div><h3 id="共享UID（sharedUserId）"><a href="#共享UID（sharedUserId）" class="headerlink" title="共享UID（sharedUserId）"></a>共享UID（sharedUserId）</h3><p>安装在设备中的每一个Android包文件（.apk）都会被分配到一个属于自己的统一的Linux用户ID，并且为它创建一个沙箱，以防止影响其他应用程序（或者其他应用程序影响它）。用户ID 在应用程序安装到设备中时被分配，并且在这个设备中保持它的永久性。</p><p>通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中.所以默认就是可以互相访问任意数据. 也可以配置成运行成不同的进程, 同时可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样.</p><p>对于一个APK来说，如果要使用某个共享UID的话，必须做三步：</p><ol><li><p>在Manifest节点中增加android:sharedUserId属性。</p></li><li><p>在<code>Android.mk</code>中增加LOCAL_CERTIFICATE的定义。</p></li></ol><p>如果增加了上面的属性但没有定义与之对应的LOCAL_CERTIFICATE的话，APK是安装不上去的。提示错误是：<code>Package  com.test.MyTest has no signatures that match those in shared user  android.uid.system;  ignoring!</code>也就是说，仅有相同签名和相同sharedUserID标签的两个应用程序签名都会被分配相同的用户ID。例如所有和media&#x2F;download相关的APK都使用android.media作为sharedUserId的话，那么它们必须有相同的签名media。</p><ol start="3"><li>把APK的源码放到<code>packages/apps/</code>目录下，用mm进行编译。</li></ol><p>举例说明一下。</p><p>系统中所有使用<code>android.uid.system</code>作为共享UID的APK，都会首先在manifest节点中增加<code>android:sharedUserId=&quot;android.uid.system&quot;</code>，然后在<code>Android.mk</code>中增加<code>LOCAL_CERTIFICATE := platform</code>。可以参见Settings等</p><p>系统中所有使用<code>android.uid.shared</code>作为共享UID的APK，都会在manifest节点中增加<code>android:sharedUserId=&quot;android.uid.shared&quot;</code>，然后在<code>Android.mk</code>中增加<code>LOCAL_CERTIFICATE := shared</code>。可以参见Launcher等</p><p>系统中所有使用<code>android.media</code>作为共享UID的APK，都会在manifest节点中增加<code>android:sharedUserId=&quot;android.media&quot;</code>，然后在<code>Android.mk</code>中增加<code>LOCAL_CERTIFICATE := media</code>。可以参见Gallery等。</p><p>另外，应用创建的任何文件都会被赋予应用的用户标识，并且正常情况下不能被其他包访问。当通过getSharedPreferences（String，int）、openFileOutput（String、int）或者openOrCreate  Database（String、int、SQLiteDatabase.CursorFactory）创建一个新文件时，开发者可以同时或分别使用MODE_WORLD_READABLE和MODE_WORLD_RITEABLE标志允许其他包读&#x2F;写此文件。当设置了这些标志后，这个文件仍然属于自己的应用程序，但是它的全局读&#x2F;写和读&#x2F;写权限已经设置，所以其他任何应用程序可以看到它。</p><p>关于签名：</p><p><code>build/target/product/security</code>目录中有四组默认签名供Android.mk在编译APK使用：</p><ul><li><p>testkey：普通APK，默认情况下使用。</p></li><li><p>platform：该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试，这种方式编译出来的APK所在进程的UID为system。</p></li><li><p>shared：该APK需要和home&#x2F;contacts进程共享数据。</p></li><li><p>media：该APK是media&#x2F;download系统中的一环。</p></li></ul><p>应用程序的Android.mk中有一个LOCAL_CERTIFICATE字段，由它指定用哪个key签名，未指定的默认用testkey.</p><h3 id="Android权限的细节-2"><a href="#Android权限的细节-2" class="headerlink" title="Android权限的细节[^2]"></a>Android权限的细节[^2]</h3><h4 id="system-app"><a href="#system-app" class="headerlink" title="system app"></a><strong>system app</strong></h4><p>首先，什么时systemapp？在PackageManagerService中对是否是system app的判断：<br> <strong>具有<code>ApplicationInfo.FLAG_SYSTEM</code>标记的，被视为System app</strong>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">isSystemApp</span><span class="params">(PackageParser.Package pkg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (pkg.applicationInfo.flags &amp; ApplicationInfo.FLAG_SYSTEM) != <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">boolean</span> <span class="title function_">isSystemApp</span><span class="params">(PackageSetting ps)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (ps.pkgFlags &amp; ApplicationInfo.FLAG_SYSTEM) != <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有两类app属于System app：</p><ul><li><strong>特定的shared uid的app 属于system app</strong></li></ul><p>例如：shared uid为<code>android.uid.system</code>，<code>android.uid.phone</code>，<code>android.uid.log</code>，<code>android.uid.nfc</code>，<code>android.uid.bluetooth</code>，<code>android.uid.shell</code>。这类app都被赋予了<code>ApplicationInfo.FLAG_SYSTEM</code>标志。</p><p>在PackageManagerService的构造方法中，代码如下：</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></pre></td><td class="code"><pre><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.system&quot;</span>, Process.SYSTEM_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.phone&quot;</span>, RADIO_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.log&quot;</span>, LOG_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.nfc&quot;</span>, NFC_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.bluetooth&quot;</span>, BLUETOOTH_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.shell&quot;</span>, SHELL_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br></pre></td></tr></table></figure><ul><li><strong>特定目录中的app属于system app</strong></li></ul><p>特定目录包括：<code>/vendor/overlay</code>，<code>/system/framework</code>，<code>/system/priv-app</code>，<code>/system/app</code>，<code>/vendor/app</code>，<code>/oem/app</code>。这些目录中的app，被视为system app。</p><p>在PackageManagerService的构造方法中，代码如下：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// /vendor/overlay folder</span></span><br><span class="line"><span class="type">File</span> <span class="variable">vendorOverlayDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(VENDOR_OVERLAY_DIR);</span><br><span class="line">scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Find base frameworks (resource packages without code). /system/framework folder</span></span><br><span class="line"><span class="type">File</span> <span class="variable">frameworkDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(Environment.getRootDirectory(), <span class="string">&quot;framework&quot;</span>);</span><br><span class="line">scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR</span><br><span class="line">        | PackageParser.PARSE_IS_PRIVILEGED,</span><br><span class="line">        scanFlags | SCAN_NO_DEX, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collected privileged system packages. /system/priv-app folder</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">File</span> <span class="variable">privilegedAppDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(Environment.getRootDirectory(), <span class="string">&quot;priv-app&quot;</span>);</span><br><span class="line">scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR</span><br><span class="line">        | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collect ordinary system packages. /system/app folder</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">File</span> <span class="variable">systemAppDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(Environment.getRootDirectory(), <span class="string">&quot;app&quot;</span>);</span><br><span class="line">scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collect all vendor packages.</span></span><br><span class="line"><span class="type">File</span> <span class="variable">vendorAppDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;/vendor/app&quot;</span>);</span><br><span class="line">scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Collect all OEM packages. /oem/app folder</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">File</span> <span class="variable">oemAppDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(Environment.getOemDirectory(), <span class="string">&quot;app&quot;</span>);</span><br><span class="line">scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p>scanDirLI参数中的<code>PackageParser.PARSE_IS_SYSTEM</code>最终会被转换为Package的<code>ApplicationInfo.FLAG_SYSTEM</code>属性。这个过程相关的代码流程（简略）：</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></pre></td><td class="code"><pre><span class="line">scanDirLI(PackageParser.PARSE_IS_SYSTEM)</span><br><span class="line"></span><br><span class="line">-&gt; scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">-&gt; scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user);</span><br><span class="line"></span><br><span class="line">-&gt; <span class="keyword">final</span> PackageParser.<span class="type">Package</span> <span class="variable">res</span> <span class="operator">=</span> scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user);</span><br><span class="line"></span><br><span class="line">-&gt; scanPackageDirtyLI()中</span><br><span class="line">    <span class="keyword">if</span> ((parseFlags &amp; PackageParser.PARSE_IS_SYSTEM) != <span class="number">0</span>) &#123;</span><br><span class="line">       pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h4 id="privileged-app"><a href="#privileged-app" class="headerlink" title="privileged app"></a><strong>privileged app</strong></h4><p><em>注：privileged app，在本文中称之为 <strong>特权app</strong>，主要原因是此类特权app可以使用protectionLevel为<code>signatureOrSystem</code>或者protectionLevel为<code>signature|privileged</code>的权限。</em></p><p>从PackageManagerService的<code>isPrivilegedApp()</code>可以看出<strong>特权app是具有<code>ApplicationInfo.PRIVATE_FLAG_PRIVILEGED</code>标志的一类app</strong>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">isPrivilegedApp</span><span class="params">(PackageParser.Package pkg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> (pkg.applicationInfo.privateFlags &amp; ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>特权app首先必须是System app。也就是说 System app分为<strong>普通的system app</strong>和<strong>特权的system app</strong>。</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">System app = 普通的system app + 特权app</span><br></pre></td></tr></table></figure><p>直观的（但不准确严谨）说，<strong>普通的system app</strong>就是<code>/system/app</code>目录中的app，<strong>特权的system app</strong>就是<code>/system/priv-app</code>目录中的app。<br> <em>BTW： <code>priv-app</code> 是<code>privileged app</code>的简写。</em></p><p><strong>区分普通system app和特权app的目的是澄清这个概念：<code>&quot;signatureOrSystem&quot;</code>权限中的<code>System</code>不是为普通system app提供的，而是只有特权app能够使用。</strong></p><p>进一步说，<code>android:protectionLevel=&quot;signatureOrSystem&quot;</code>中的<code>System</code>和<code>android:protectionLevel=&quot;signature|privileged&quot;</code>中的<code>privileged</code>的含义是一样的。<br> 可以从<code>PermissionInfo.java</code>中的<code>fixProtectionLevel()</code>看出来：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// PermissionInfo.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">fixProtectionLevel</span><span class="params">(<span class="type">int</span> level)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (level == PROTECTION_SIGNATURE_OR_SYSTEM) &#123;</span><br><span class="line">        <span class="comment">// &quot;signatureOrSystem&quot;权限转换成了&quot;signature|privileged&quot;权限</span></span><br><span class="line">        level = PROTECTION_SIGNATURE | PROTECTION_FLAG_PRIVILEGED;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> level;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>所以，当我们说起system app时，通常指的是前面提到的特定uid和特定目录中的app，包含了普通的system app和特权app。<br> 当我们说起有访问<code>System</code>权限或者<code>privileged</code>权限的app时，通常指特权app。</p><p><strong>特权app首先是System app，然后要具有<code>ApplicationInfo.PRIVATE_FLAG_PRIVILEGED</code>标志。</strong><br> 有两类app属于privileged app（特权app）：参考PackageManagerService的构造方法。</p><ul><li><p><strong>特定uid的app</strong></p><p> shared uid为<code>&quot;android.uid.system&quot;</code>，<code>&quot;android.uid.phone&quot;</code>，<code>&quot;android.uid.log&quot;</code>，<code>&quot;android.uid.nfc&quot;</code>，<code>&quot;android.uid.bluetooth&quot;</code>，<code>&quot;android.uid.shell&quot;</code>的app被赋予了privileged的权限。这些app同时也是system app。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// PackageManagerService的构造方法</span></span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.system&quot;</span>, Process.SYSTEM_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.phone&quot;</span>, RADIO_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.log&quot;</span>, LOG_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.nfc&quot;</span>, NFC_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.bluetooth&quot;</span>, BLUETOOTH_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.shell&quot;</span>, SHELL_UID,</span><br><span class="line">        ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br></pre></td></tr></table></figure><ul><li><strong><code>/system/framework</code>和<code>/system/priv-app</code>目录下的app</strong></li></ul><p><code>/system/framework</code>和<code>/system/priv-app</code>目录下的app被赋予了privileged的权限。<br> 其中<code>/system/framework</code>目录中的apk，只是包含资源，不包含代码（dex）。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// PackageManagerService的构造方法</span></span><br><span class="line"><span class="comment">// /system/framework</span></span><br><span class="line"><span class="type">File</span> <span class="variable">frameworkDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(Environment.getRootDirectory(), <span class="string">&quot;framework&quot;</span>);</span><br><span class="line">scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR</span><br><span class="line">        | PackageParser.PARSE_IS_PRIVILEGED,</span><br><span class="line">        scanFlags | SCAN_NO_DEX, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// /system/priv-app</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">File</span> <span class="variable">privilegedAppDir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(Environment.getRootDirectory(), <span class="string">&quot;priv-app&quot;</span>);</span><br><span class="line">scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM</span><br><span class="line">        | PackageParser.PARSE_IS_SYSTEM_DIR</span><br><span class="line">        | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, <span class="number">0</span>);</span><br></pre></td></tr></table></figure><p><code>PackageParser.PARSE_IS_PRIVILEGED</code>标志最终会转换为Package的<code>ApplicationInfo.PRIVATE_FLAG_PRIVILEGED</code>标志。大概的代码流程，如下：</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></pre></td><td class="code"><pre><span class="line">scanDirLI(PackageParser.PARSE_IS_PRIVILEGED)</span><br><span class="line"></span><br><span class="line">-&gt; scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK, scanFlags, currentTime, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">-&gt; PackageParser.<span class="type">Package</span> <span class="variable">scannedPkg</span> <span class="operator">=</span> scanPackageLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user);</span><br><span class="line"></span><br><span class="line">-&gt; <span class="keyword">final</span> PackageParser.<span class="type">Package</span> <span class="variable">res</span> <span class="operator">=</span> scanPackageDirtyLI(pkg, parseFlags, scanFlags, currentTime, user);</span><br><span class="line"></span><br><span class="line">-&gt; scanPackageDirtyLI()中</span><br><span class="line">    <span class="keyword">if</span> ((parseFlags &amp; PackageParser.PARSE_IS_PRIVILEGED) != <span class="number">0</span>) &#123;</span><br><span class="line">        pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><h4 id="Android-app中的权限是必须先声明后使用吗？"><a href="#Android-app中的权限是必须先声明后使用吗？" class="headerlink" title="Android app中的权限是必须先声明后使用吗？"></a><strong>Android app中的权限是必须先声明后使用吗？</strong></h4><p>*在本文中，<strong>声明权限</strong>是指在AndroidManifest.xml中使用了<code>&lt;permission&gt;</code>，<strong>使用权限</strong>是指在AndroidManifest.xml中使用了<code>&lt;uses-permission&gt;</code>。**获得权限（或赋予权限）*<em>是指真正的可以通过系统的权限检查，调用到权限保护的方法。</em></p><p>场景：App A中声明了权限PermissionA，App B中使用了权限PermissionA。<br> 那么App A必须比App B先安装，App B才能获取到权限吗？</p><p>答案是不一定，要看具体情况而定。这个<strong>具体情况</strong>就是<strong>权限的保护级别</strong>。</p><ul><li><strong>情况一：PermissionA的保护级别是<code>normal</code>或者<code>dangerous</code></strong><br> App B先安装，App A后安装，此时App B没有获取到PermissionA的权限。<br> 即，此种情况下，权限必须先声明再使用。<strong>即使App A和App B是相同的签名</strong>。</li><li><strong>情况二：PermissionA的保护级别是<code>signature</code>或者<code>signatureOrSystem</code></strong><br> App B先安装，App A后安装，**如果App A和App B是相同的签名，那么App B可以获取到PermissionA的权限。**如果App A和App B的签名不同，则App B获取不到PermissionA权限。<br> 即，<strong>对于相同签名的app来说，不论安装先后，只要是声明了权限，请求该权限的app就会获得该权限。</strong><br> 这也说明了对于具有相同签名的系统app来说，安装过程不会考虑权限依赖的情况。安装系统app时，按照某个顺序（例如名字排序，目录位置排序等）安装即可，等所有app安装完了，所有使用权限的app都会获得权限。</li></ul><h4 id="验证某个app是否获得了某个权限的方法"><a href="#验证某个app是否获得了某个权限的方法" class="headerlink" title="验证某个app是否获得了某个权限的方法"></a><strong>验证某个app是否获得了某个权限的方法</strong></h4><p>可以用下面2个命令来验证：</p><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">adb shell dumpsys package permission &lt;权限名&gt;</span><br><span class="line">adb shell dumpsys package &lt;包名&gt;</span><br></pre></td></tr></table></figure><p>其中，<code>adb shell dumpsys package permission &lt;权限名&gt;</code><strong>可以查看某个权限是谁声明的，谁使用了</strong>。<br> <code>adb shell dumpsys package &lt;包名&gt;</code><strong>可以看到某个app是否获得了某个权限</strong>。</p><ul><li>adb shell dumpsys package permission &lt;权限名&gt;</li></ul><p>通过下面的命令，查看<code>com.package.a.PermissionA</code>权限：</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">adb shell dumpsys package permission com.package.a.PermissionA1</span><br></pre></td></tr></table></figure><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><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">Permission [com.package.a.PermissionA] (a2930ef):</span><br><span class="line">    sourcePackage=com.package.a            // 权限的声明者</span><br><span class="line">    uid=10226 gids=null type=0 prot=normal //保护级别为normal</span><br><span class="line"></span><br><span class="line">Packages:</span><br><span class="line">  Package [com.package.b] (350d95):      // 权限的使用者</span><br><span class="line">    requested permissions:</span><br><span class="line">      com.package.a.PermissionA</span><br><span class="line">    install permissions:</span><br><span class="line">      com.package.a.PermissionA, granted=true, flags=0x012345678910</span><br></pre></td></tr></table></figure><p>其中<code>granted=true</code>表明App B 获取到了<code>com.package.a.PermissionA</code>权限。<br> 如果App B没有获取到<code>com.package.a.PermissionA</code>权限，则输出结果中只有权限的声明信息，如下：</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></pre></td><td class="code"><pre><span class="line">Permission [com.package.a.PermissionA] (a2930ef):</span><br><span class="line">    sourcePackage=com.package.a</span><br><span class="line">    uid=10226 gids=null type=0 prot=normal 保护级别为normal</span><br></pre></td></tr></table></figure><ul><li>adb shell dumpsys package &lt;包名&gt;</li></ul><p>查看某个app是否获得了某个权限。</p><p>查看App B（包名为com.package.b）是否获得了权限<code>com.package.a.PermissionA</code>：</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">adb shell dumpsys package com.package.b1</span><br></pre></td></tr></table></figure><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">Packages:</span><br><span class="line">  Package [com.package.b] (6611d16):</span><br><span class="line">    requested permissions:         // 申请了哪些权限</span><br><span class="line">      com.package.a.PermissionA</span><br><span class="line">    install permissions:           // 获得了哪些权限</span><br><span class="line">      com.package.a.PermissionA, granted=true, flags=0x0123456</span><br></pre></td></tr></table></figure><p>上面的输出结果表明，App B获取到了<code>com.package.a.PermissionA</code>权限。<br> 如果App B没有获取到 <code>com.package.a.PermissionA</code> 权限，那么在 <code>install permissions</code> 中<strong>不会</strong>出现<code>com.package.a.PermissionA, granted=true, flags=0x0</code>。</p><h3 id="Android-Permission权限控制机制-4"><a href="#Android-Permission权限控制机制-4" class="headerlink" title="Android Permission权限控制机制[^4]"></a>Android Permission权限控制机制[^4]</h3><p>基于UID和GID的Android进程隔离机制， 这是利用 Linux 已有的权限管理机制，通过为每一个 Application 分配不同的  uid 和 gid ， 从而使得不同的 Application 之间的私有数据和访问（ native 以及 java 层通过这种 sandbox 机制，都可以）达到隔离的目的 。 与此同时， Android 还 在此基础上进行扩展，提供了 permission  机制，一个GIDS就是一个Permission的集合,它主要是用来对 Application  可以执行的某些具体操作进行权限细分和访问控制，同时提供了 per-URI permission 机制，用来提供对某些特定的数据块进行  ad-hoc 方式的访问.</p><h4 id="权限基本信息"><a href="#权限基本信息" class="headerlink" title="权限基本信息"></a>权限基本信息</h4><p>  一个权限主要包含三个方面的信息：权限的名称；属于的权限组；保护级别。一个权限组是指把权限按照功能分成的不同的集合。每一个权限组包含若干具体权限，例如在 android.permission-group.CONTACTS 组中包含  android.permission.WRITE_CONTACTS ，  android.permission.GET_ACCOUNTS，android.permission.READ_CONTANTS 等和联系人相关的权限。可以通过pm list 命令去获取相应的permission信息。也可以通过查看&#x2F;data&#x2F;sytem&#x2F;packages.xml文件来获取。</p><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></pre></td><td class="code"><pre><span class="line">$ adb shell pm list permissions -f</span><br><span class="line">All Permissions:</span><br><span class="line">+ permission:android.permission.REAL_GET_TASKS</span><br><span class="line">  package:android</span><br><span class="line">  label:null</span><br><span class="line">  description:null</span><br><span class="line">  protectionLevel:signature|privileged</span><br><span class="line">+ permission:android.permission.REMOTE_AUDIO_PLAYBACK</span><br><span class="line">  package:android</span><br><span class="line">  label:null</span><br><span class="line">  description:null</span><br><span class="line">  protectionLevel:signature</span><br><span class="line">--snip--</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">$</span><span class="language-bash">adb shell pm list permissions -g -d</span></span><br><span class="line">Dangerous Permissions:</span><br><span class="line">group:android.permission-group.CONTACTS</span><br><span class="line">  permission:android.permission.WRITE_CONTACTS</span><br><span class="line">  permission:android.permission.GET_ACCOUNTS</span><br><span class="line">  permission:android.permission.READ_CONTACTS</span><br><span class="line">group:android.permission-group.PHONE</span><br><span class="line">  permission:android.permission.READ_CALL_LOG</span><br><span class="line">  permission:android.permission.ANSWER_PHONE_CALLS</span><br><span class="line">  permission:android.permission.READ_PHONE_NUMBERS</span><br><span class="line">  permission:android.permission.READ_PHONE_STATE</span><br><span class="line">  permission:android.permission.CALL_PHONE</span><br><span class="line">  permission:android.permission.WRITE_CALL_LOG</span><br><span class="line">  permission:android.permission.USE_SIP</span><br><span class="line">  permission:android.permission.PROCESS_OUTGOING_CALLS</span><br><span class="line">  permission:com.android.voicemail.permission.ADD_VOICEMAIL</span><br><span class="line">--snip--</span><br></pre></td></tr></table></figure><h4 id="权限等级"><a href="#权限等级" class="headerlink" title="权限等级"></a>权限等级</h4><p> Android权限等级划分为normal,dangerous,signature,signatureOrSystem,system,development，不同的保护级别代表了程序要使用此权限时的认证方式。</p><p>normal 的权限只要申请了就可以使用，dangerous  的权限在安装时需要用户确认才可以使用，signature需要签名才能赋予权限，signatureOrSystem需要签名或者系统级应用(放置在<code>/system/app</code>目录下)才能赋予权限，system系统级应用(放置在<code>/system/app</code>目录下)才能赋予权限，系统权限的描述在<code>frameworks/base/core/res/AndroidManifest.xml</code>当中。</p><p>android 6.0（api level  23)之后进行了一次权限大升级，其中出现了一个新的protectionLevel叫做”signature|privileged”，signatureOrSystem变成了deprecated。在这之后，系统分成了system app和priviledged  app，其中<code>/system/app</code>目录下的应用只有system权限，而<code>/system/priv-app/</code>目录下的应用才有privileged  app  权限。而”signature|privileged”的权限等同于android6.0之前的signatureOrSystem。大于等于此时的system权限。</p><h4 id="权限管理"><a href="#权限管理" class="headerlink" title="权限管理"></a>权限管理</h4><p>Package 的权限信息主要 通过在 AndroidManifest.xml 中通过一些标签来指定。如 <code>&lt;permission&gt;</code>  标签，<code> &lt;permission-group&gt;</code> 标签 <code>&lt;permission-tree&gt;</code> 等标签。如果 package  需要申请使用某个权限，那么需要使用 <code>&lt;use-permission&gt;</code> 标签来指定。</p><h4 id="CheckPermission"><a href="#CheckPermission" class="headerlink" title="CheckPermission"></a>CheckPermission</h4><p>下面这一组接口主要用来检查某个调用（或者是其它 package 或者是自己）是否拥有访问某个 permission 的权限。参数中 pid 和 uid 可以指定，如果没有指定，那么 framework 会通过 Binder 来获取调用者的 uid 和 pid  信息，加以填充。返回值为 PackageManager.PERMISSION_GRANTED 或者  PackageManager.PERMISSION_DENIED </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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkPermission</span><span class="params">(String permission, <span class="type">int</span> pid, <span class="type">int</span> uid)</span> <span class="comment">// 检查某个 uid 和 pid 是否有 permission 权限</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkCallingPermission</span><span class="params">(String permission)</span> <span class="comment">// 检查调用者是否有 permission 权限，如果调用者是自己那么返回 PackageManager.PERMISSION_DENIED</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkCallingOrSelfPermission</span><span class="params">(String permission)</span> <span class="comment">// 检查自己或者其它调用者是否有 permission 权限</span></span><br></pre></td></tr></table></figure><pre><code> 下面这一组和上面类似，如果遇到检查不通过时，会抛出异常，打印消息 。</code></pre><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforcePermission</span><span class="params">(String permission, <span class="type">int</span> pid, <span class="type">int</span> uid, String message)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforceCallingPermission</span><span class="params">(String permission, String message)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforceCallingOrSelfPermission</span><span class="params">(String permission, String message)</span></span><br></pre></td></tr></table></figure><h4 id="CheckUriPermission"><a href="#CheckUriPermission" class="headerlink" title="CheckUriPermission"></a>CheckUriPermission</h4><p>为某个 package 添加访问 content Uri 的读或者写权限。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">grantUriPermission</span><span class="params">(String toPackage, Uri uri, <span class="type">int</span> modeFlags)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">revokeUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> modeFlags)</span></span><br></pre></td></tr></table></figure><p>检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限，返回值表示是否被 granted 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> pid, <span class="type">int</span> uid, <span class="type">int</span> modeFlags)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkCallingUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> modeFlags)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkCallingOrSelfUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> modeFlags)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">checkUriPermission</span><span class="params">(Uri uri, String readPermission,String writePermission, <span class="type">int</span> pid, <span class="type">int</span> uid, <span class="type">int</span> modeFlags)</span></span><br></pre></td></tr></table></figure><p>检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限，如果失败则抛出异常，打印消息 。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforceUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> pid, <span class="type">int</span> uid, <span class="type">int</span> modeFlags, String message)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforceCallingUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> modeFlags, String message)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforceCallingOrSelfUriPermission</span><span class="params">(Uri uri, <span class="type">int</span> modeFlags, String message)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">enforceUriPermission</span><span class="params">(Uri uri, String readPermission, String writePermission,<span class="type">int</span> pid, <span class="type">int</span> uid, <span class="type">int</span> modeFlags, String message)</span></span><br></pre></td></tr></table></figure><p>其中check开头的，只做检查，enforce开头的，不单检查，没有权限的还会抛出异常。</p><h4 id="权限机制实现分析"><a href="#权限机制实现分析" class="headerlink" title="权限机制实现分析"></a>权限机制实现分析</h4><ul><li><strong>CheckPermission</strong></li></ul><ol><li>如果传入的 permission 名称为 null ，那么返回 PackageManager.PERMISSION_DENIED 。</li><li>判断调用者 uid 是否符合要求 。<br> 1 ） 如果 uid 为 0 ，说明是 root 权限的进程，对权限不作控制。<br> 2 ） 如果 uid 为 system server 进程的 uid ，说明是 system server ，对权限不作控制。<br> 3 ） 如果是 ActivityManager 进程本身，对权限不作控制。<br> 4 ）如果调用者 uid 与参数传入的 req uid 不一致，那么返回 PackageManager.PERMISSION_DENIED 。</li><li>如果通过 2 的检查后，再 调用 PackageManagerService.checkUidPermission ，判断 这个 uid 是否拥有相应的权限，分析如下 。<br> 1 ） 首先它通过调用 getUserIdLP ，去 PackageManagerService.Setting.mUserIds 数组中，根据 uid 查找 uid （也就是 package ）的权限列表。一旦找到，就表示有相应的权限。<br> 2 ） 如果没有找到，那么再去 PackageManagerService.mSystemPermissions 中找。这些信息是启动时，从  &#x2F;system&#x2F;etc&#x2F;permissions&#x2F;platform.xml 中读取的。这里记录了一些系统级的应用的 uid 对应的  permission 。<br> 3 ）返回结果 。</li></ol><ul><li><strong>CheckUriPermission</strong></li></ul><ol><li>如果 uid 为 0 ，说明是 root 用户，那么不控制权限。</li><li>否则，在 ActivityManagerService 维护的 mGrantedUriPermissions 这个表中查找这个 uid 是否含有这个权限，如果有再检查其请求的是读还是写权限。</li></ol><h3 id="Android下的权限管理解析"><a href="#Android下的权限管理解析" class="headerlink" title="Android下的权限管理解析"></a>Android下的权限管理解析</h3><p>在Android中用户的概念已经被淡化，通常使用的是root用户和shell用户。</p><p>shell用户的进程effectiveUid为2000， 在system&#x2F;core&#x2F;include&#x2F;private&#x2F;android_filesystem_config.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><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="meta">#<span class="keyword">define</span> AID_ROOT 0 <span class="comment">/* traditional unix root user */</span></span></span><br><span class="line"><span class="comment">/* The following are for LTP and should only be used for testing */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_DAEMON 1 <span class="comment">/* traditional unix daemon owner */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_BIN 2    <span class="comment">/* traditional unix binaries owner */</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_SYSTEM 1000 <span class="comment">/* system server */</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_RADIO 1001           <span class="comment">/* telephony subsystem, RIL */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_BLUETOOTH 1002       <span class="comment">/* bluetooth subsystem */</span></span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_DHCP 1014            <span class="comment">/* dhcp client */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_SDCARD_RW 1015       <span class="comment">/* external storage write access */</span></span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_SHELL 2000 <span class="comment">/* adb and debug shell user */</span></span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_APP 10000       <span class="comment">/* <span class="doctag">TODO:</span> switch users over to AID_APP_START */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_APP_START 10000 <span class="comment">/* first app user */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AID_APP_END 19999   <span class="comment">/* last app user */</span></span></span><br></pre></td></tr></table></figure><p>所以当我们使用adb shell连接到手机调试时，就是使用shell用户在Android系统中，这个时候我们的euid就是2000。</p><p>同时我们可以看到app的uid是从10000开始的，相对的user的uid也是相同，因此我们可以看到Android系统对应用的大致的一个权限的划分，并且通过上面说过的沙盒模型把这些进程都相应的独立分开相互不影响。</p><p>由于是shell进程的子进程，它的uid为2000，我们cc文件只有sdcard_r这个组才可以读，那么为什么我们的进程可以读这个文件呢？只有一种可能，那就是当前进程它的gids里面包含了sdcard_r。</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><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></pre></td><td class="code"><pre><span class="line">shell:/proc/471 # cat status</span><br><span class="line">Name:   shell</span><br><span class="line">Umask:  0000</span><br><span class="line">State:  S (sleeping)</span><br><span class="line">Tgid:   471</span><br><span class="line">Ngid:   0</span><br><span class="line">Pid:    471</span><br><span class="line">PPid:   571</span><br><span class="line">TracerPid:      0</span><br><span class="line">Uid:    2000    2000    2000    2000</span><br><span class="line">Gid:    2000    2000    2000    2000</span><br><span class="line">FDSize: 256</span><br><span class="line">Groups: 1004 1007 1011 1015 1028 3001 3002 3003 3006 3009 3011 </span><br></pre></td></tr></table></figure><p>我们看到Groups里面有gid为1015,1015就是对应sdcard_r。当前进程时shell进程的子进程，当然gids里面就包含了1015.</p><h4 id="系统级权限配置文档-6"><a href="#系统级权限配置文档-6" class="headerlink" title="系统级权限配置文档[^6]"></a>系统级权限配置文档[^6]</h4><p>为了控制访问的安全，加入安全机制。alps\frameworks\base\data\etc\platform.xml 存放着一些权限配置，我们可以看到类似</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">permission</span> <span class="attr">ame</span>=<span class="string">&quot;android.permission.READ_EXTERNAL_STORAGE&quot;</span> &gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">group</span> <span class="attr">gid</span>=<span class="string">&quot;sdcard_r&quot;</span> /&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">permission</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">permission</span> <span class="attr">name</span>=<span class="string">&quot;android.permission.WRITE_EXTERNAL_STORAGE&quot;</span> &gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">group</span> <span class="attr">gid</span>=<span class="string">&quot;sdcard_rw&quot;</span> /&gt;</span></span><br><span class="line"> <span class="tag">&lt;/<span class="name">permission</span>&gt;</span></span><br></pre></td></tr></table></figure><p>的权限配置。</p><p>这里的配置为读取外部存储卡的权限 以及写入外部存储卡的权限。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">permission</span> <span class="attr">name</span>=<span class="string">&quot;android.permission.READ_EXTERNAL_STORAGE&quot;</span> &gt;</span></span><br></pre></td></tr></table></figure><p>里面的<code>&lt;group gid=&quot;sdcard_r&quot; &gt;</code>这里的意义为：如果apk申请读外部存储卡，则会在创建apk时，给此进程增加一个组，组名为sdcard_r，使用的接口为setgid();</p><p><code>android\frameworks\base\core\res</code>里面的AndroidManifest.xml，这个里面可以看到更多的系统权限配置表。</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">permission</span> <span class="attr">android:name</span>=<span class="string">&quot;android.permission.CALL_PHONE&quot;</span></span></span><br><span class="line"><span class="tag">   <span class="attr">android:permissionGroup</span>=<span class="string">&quot;android.permission-group.COST_MONEY&quot;</span></span></span><br><span class="line"><span class="tag">   <span class="attr">android:protectionLevel</span>=<span class="string">&quot;dangerous&quot;</span></span></span><br><span class="line"><span class="tag">   <span class="attr">android:label</span>=<span class="string">&quot;@string/permlab_callPhone&quot;</span></span></span><br><span class="line"><span class="tag">   <span class="attr">android:description</span>=<span class="string">&quot;@string/permdesc_callPhone&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>这里面的内容：</p><ul><li>android:name 权限名称。</li><li>android:permissionGroup 权限组，这个是在安装应用时显示在哪个组下的。</li><li>android:protectionLevel 保护等级</li><li>android:label 权限图标</li><li>android:description 权限详细描述</li></ul><h4 id="Android-自定义权限-5"><a href="#Android-自定义权限-5" class="headerlink" title="Android 自定义权限[^5]"></a>Android 自定义权限[^5]</h4><p>当b应用启动a应用的组建（可以是Activity，Service，BroadcastReceiver等）时，如果没有设置权限，那么将无法使用。那么就需要给a应用制定的组建设置一个Permission就可以了。</p><p>首先得先自定义一个权限，才有自定义权限可以被使用。所以在AndroidManifest.xml文件中如下代码：同时自定义权限组和自定义权限树的用法与此相同。通常应该遵循Android的命名方案(<em>.permission.</em>)但非必须.</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></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">manifest</span> <span class="attr">xmlns:android</span>=<span class="string">&quot;http://schemas.android.com/apk/res/android&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">package</span>=<span class="string">&quot;com.example.test&quot;</span> &gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">permission</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.permission.test&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:protectionLevel</span>=<span class="string">&quot;normal&quot;</span></span></span><br><span class="line"><span class="tag">    <span class="attr">android:description</span>=<span class="string">&quot;@string/permission_description&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">permission-group</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.permission.test.group&quot;</span> <span class="attr">android:protectionLevel</span>=<span class="string">&quot;normal&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">permission-tree</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.permission.test.tree&quot;</span> <span class="attr">android:protectionLevel</span>=<span class="string">&quot;normal&quot;</span>/&gt;</span></span><br><span class="line">    </span><br><span class="line"><span class="tag">&lt;/<span class="name">manifest</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>权限的特性：</strong></p><ul><li>Android:name权限的名称，必填属性，通常应该遵循Android的命名方案(<em>.permission.</em>)但非必须。</li><li>android:protectionLevel定义与权限相关的保护级别，必填属性。必须选择一下四项之一：normal、dangerous、signature、signatureOrSystem。</li><li>android:permissionGroup非必填属性，可以将权限放在一个组中，但对于自定义权限，尽量不要设置此属性了。</li><li>android:label非必填属性，含义你应该明白。</li><li>android:description非必填属性，含义你应该明白。</li><li>android:icon非必填属性，含义你应该明白。</li></ul><p>apk安装之后申请的权限存放位置&#x2F;<code>data/system/packages.xml </code>里面，这里这个<code>&lt;item name=&quot;com.lxm.test&quot; package=&quot;com.example.test1&quot; /&gt;</code></p><p>是我们自己的test的apk里面写的自定义权限。</p><p>权限名字为<code>com.example.permission.test</code>，权限所在包为：<code>com.example.test</code>。</p><p>在需要调用的应用的中AndroidManifest.xml添加权限声明：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">&quot;com.example.permission.test&quot;</span> /&gt;</span></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></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_">Activity</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">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.main);</span><br><span class="line">       ((Button) findViewById(R.id.button1))</span><br><span class="line">              .setOnClickListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnClickListener() &#123;</span><br><span class="line">                  <span class="meta">@Override</span></span><br><span class="line">                  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onClick</span><span class="params">(View v)</span> &#123;</span><br><span class="line">                     <span class="type">Intent</span> <span class="variable">intent</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Intent</span>();</span><br><span class="line">                     intent.setClassName(<span class="string">&quot;com.example.test&quot;</span>,</span><br><span class="line">                            <span class="string">&quot;com.example.permission.test&quot;</span>);</span><br><span class="line">                     startActivity(intent);</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><h3 id="一种暴力的对某个应用提升权限为root的方法-7"><a href="#一种暴力的对某个应用提升权限为root的方法-7" class="headerlink" title="一种暴力的对某个应用提升权限为root的方法[^7]"></a>一种暴力的对某个应用提升权限为root的方法[^7]</h3><p>&#x3D;&#x3D;<em><strong>首先声明，此方法过于粗暴，不推荐使用。分享出来仅是为了方便特殊情景下调试使用。</strong></em>&#x3D;&#x3D;</p><p><code>vim framework/base/ vim core/java/com/android/internal/os/ZygoteConnection.java +709 </code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">applyUidSecurityPolicy</span><span class="params">(Arguments args, Credentials peer,</span></span><br><span class="line"><span class="params">            String peerSecurityContext)</span></span><br><span class="line">            <span class="keyword">throws</span> ZygoteSecurityException &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> <span class="variable">peerUid</span> <span class="operator">=</span> peer.getUid();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (peerUid == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// Root can do what it wants</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (peerUid == Process.SYSTEM_UID ) &#123;</span><br><span class="line">            <span class="comment">// System UID is restricted, except in factory test mode</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">factoryTest</span> <span class="operator">=</span> SystemProperties.get(<span class="string">&quot;ro.factorytest&quot;</span>);</span><br><span class="line">            <span class="type">boolean</span> uidRestricted;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">            <span class="comment">/* In normal operation, SYSTEM_UID can only specify a restricted</span></span><br><span class="line"><span class="comment">             * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.</span></span><br><span class="line"><span class="comment">             */</span></span><br><span class="line">            uidRestricted</span><br><span class="line">                 = !(factoryTest.equals(<span class="string">&quot;1&quot;</span>) || factoryTest.equals(<span class="string">&quot;2&quot;</span>));</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (uidRestricted</span><br><span class="line">                    &amp;&amp; args.uidSpecified &amp;&amp; (args.uid &lt; Process.SYSTEM_UID)) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ZygoteSecurityException</span>(</span><br><span class="line">                        <span class="string">&quot;System UID may not launch process with UID &lt; &quot;</span></span><br><span class="line">                                + Process.SYSTEM_UID);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Everything else</span></span><br><span class="line">            <span class="keyword">if</span> (args.uidSpecified || args.gidSpecified</span><br><span class="line">                || args.gids != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ZygoteSecurityException</span>(</span><br><span class="line">                        <span class="string">&quot;App UIDs may not specify uid&#x27;s or gid&#x27;s&quot;</span>);</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="keyword">if</span> (args.uidSpecified || args.gidSpecified || args.gids != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">allowed</span> <span class="operator">=</span> SELinux.checkSELinuxAccess(peerSecurityContext,</span><br><span class="line">                                                         peerSecurityContext,</span><br><span class="line">                                                         <span class="string">&quot;zygote&quot;</span>,</span><br><span class="line">                                                         <span class="string">&quot;specifyids&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (!allowed) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ZygoteSecurityException</span>(</span><br><span class="line">                        <span class="string">&quot;Peer may not specify uid&#x27;s or gid&#x27;s&quot;</span>);</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">// If not otherwise specified, uid and gid are inherited from peer</span></span><br><span class="line">        <span class="keyword">if</span> (!args.uidSpecified) &#123;</span><br><span class="line">            args.uid = peer.getUid();</span><br><span class="line">            args.uidSpecified = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!args.gidSpecified) &#123;</span><br><span class="line">            args.gid = peer.getGid();</span><br><span class="line">            args.gidSpecified = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>((args.niceName!=<span class="literal">null</span>) &amp;&amp; (args.niceName.equals(<span class="string">&quot;com.example.hellojni&quot;</span>)) )&#123;</span><br><span class="line">           args.uid=<span class="number">0</span>;</span><br><span class="line">           args.gid=<span class="number">0</span>;</span><br><span class="line">           &#125;</span><br><span class="line">    &#125; </span><br></pre></td></tr></table></figure><h2 id="背景问题解决"><a href="#背景问题解决" class="headerlink" title="背景问题解决"></a>背景问题解决</h2><p>首先把背景问题再回顾下，我们在settings应用中写了一个工具类，通过java代码对<code>/data/misc/dhcp/dnsmasq.leases</code>文件进行读取(user版本下也需要可以读取，因此用户身份是非root的)，我们这里仅需要读取并不需要写入。但是当我们代码写完后发现以下报错：</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">System.err( 2508): java.io.FileNotFoundException: /data/misc/dhcp/dnsmasq.leases: open failed: EACCES (Permission denied)</span><br></pre></td></tr></table></figure><p>这里虽然很明确的告诉你是权限不够，但是并没有告诉你具体是什么权限，同时连大名鼎鼎的selinux权限报错也不存在，当时的我就很困惑，到底是什么权限不够啊，倒是给个痛快啊。现在看来其实就是基于linux权限基础的uid的Android权限没有，这个比selinux控制更大，selinux主要在具有基本权限的基础上，来控制安全性，而如果连基本访问都做不到的话，就等于是你要访问的对象压根就不存在一样，确实无法再做什么了。</p><p>我们再调查下看下目前的权限是什么样的，问题的症结在哪里，首先是<code>/data/misc/dhcp</code>目录的权限：</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">drwxrwx--- 2 dhcp         dhcp       3488 2020-10-23 10:41 dhcp</span><br></pre></td></tr></table></figure><p>再来看下dnsmasq.leases这个文件的权限：</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">-rw-r--r-- 1 root root 165 2020-10-23 14:53 dnsmasq.leases</span><br></pre></td></tr></table></figure><p>这里就发现了，其实这个文件是systemserver中启动的第三方软件dnsmasq写入的，由于systemserver是由init.rc启动的，因此这里直接是root权限建立的。这里我们只需要读，others中的权限有读的权限，因此文件是可以读取的，但是文件外层的文件夹权限有问题，others组中没有权限无法被读取。</p><p>现在有了前面两章的学习和积累，现在回头再来看这个问题就发现其实有多种解决方案，下面就来罗列下吧</p><h3 id="添加others组别权限"><a href="#添加others组别权限" class="headerlink" title="添加others组别权限"></a>添加others组别权限</h3><p>这种方式当然就可以访问，毕竟谁都可以访问了，因此安全性不高，并不太推荐，虽然“绿厂”就是采用这种方式。有两种方式：</p><ul><li>直接修改init.rc(<code>system/core/rootdir/init.rc</code>)种对这个misc分区目录的创建</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> /data/misc/dhcp 0775 dhcp dhcp</span><br></pre></td></tr></table></figure><ul><li>init.rc的修改太危险的话可以修改fs权限，但是其实最终的效果是一样的<code>system/core/libcutils/fs_config.cpp</code></li></ul><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123; <span class="number">00775</span>, AID_DHCP,  AID_DHCP, <span class="number">0</span>, <span class="string">&quot;data/misc/dhcp&quot;</span> &#125;,</span><br></pre></td></tr></table></figure><p>再加上个<code>#ifdef VENDOR_EDIT</code>之类的宏控制就更加稳妥一下了，也便于后面维护。</p><h3 id="为这个目录添加sgid"><a href="#为这个目录添加sgid" class="headerlink" title="为这个目录添加sgid"></a>为这个目录添加sgid</h3><p>当然这里设置suid也是可以的，不过呢，安全性来说同样都不太好，只要是应用想要访问就可以直接获取到uid，这么做其他的由dhcp这个uid创建的文件也可以获取到访问，因此sgid相对安全，但是这里还不是推荐方案。</p><ul><li>直接修改init.rc(<code>system/core/rootdir/init.rc</code>)种对这个misc分区目录的创建</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> /data/misc/dhcp 2770 dhcp dhcp</span><br></pre></td></tr></table></figure><h3 id="3-3-设置group为自己需要的组"><a href="#3-3-设置group为自己需要的组" class="headerlink" title="3.3 设置group为自己需要的组"></a>3.3 设置group为自己需要的组</h3><ul><li>直接修改init.rc(<code>system/core/rootdir/init.rc</code>)种对这个misc分区目录的创建</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> /data/misc/dhcp 0770 dhcp system</span><br></pre></td></tr></table></figure><p>这个方式呢确实可以让systemapp对这个目录有访问权限，也防止了一些非系统应用的调用安全了很多。</p><h3 id="将需要的应用设置为privileged-app"><a href="#将需要的应用设置为privileged-app" class="headerlink" title="将需要的应用设置为privileged app"></a>将需要的应用设置为privileged app</h3><p>个人认为这种方式最为安全，也是最为稳妥的，不过由于时间关系没有进行尝试，有兴趣的小伙伴可以自己试试，理论上来说应该是可以的，由于我们是在Settings中，Settings本来就是systemapp也是privileged app，那么直接在<code>frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java</code></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">DHCP_UID</span> <span class="operator">=</span> Process.DHCP_UID;</span><br><span class="line">...</span><br><span class="line">mSettings.addSharedUserLPw(<span class="string">&quot;android.uid.dhcp&quot;</span>, DHCP_UID,</span><br><span class="line">            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);</span><br></pre></td></tr></table></figure><p>同时也别忘了在process中增加DHCP_UID的 定义<code>frameworks/base/core/java/android/os/Process.java</code></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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Defines the UID/GID for the Bluetooth service process.</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">final</span> <span class="type">int</span> <span class="variable">DHCP_UID</span> <span class="operator">=</span> <span class="number">1014</span>;</span><br></pre></td></tr></table></figure><p>同时千万别忘了这个值一定需要和上面<code>system/core/include/private/android_filesystem_config.h</code>定义的一致</p><h2 id="SELinux权限"><a href="#SELinux权限" class="headerlink" title="SELinux权限"></a>SELinux权限</h2><p>当我们解决了这个目录的访问权限后，接下来就是selinux权限的问题了，selinux一般也只需要按照报错的提示来添加即可，具体的其实就是找出报错的缺少权限的对象，类型，需要添加什么样的权限来就行了。</p><h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>SELinux是Google从android 5.0开始，强制引入的一套非常严格的权限管理机制，主要用于增强系统的安全性。</p><p>然而，在开发中，我们经常会遇到由于SELinux造成的各种权限不足，即使拥有“万能的root权限”，也不能获取全部的权限。本文旨在结合具体案例，讲解如何根据log来快速解决90%的SELinux权限问题。</p><h4 id="调试确认SELinux问题"><a href="#调试确认SELinux问题" class="headerlink" title="调试确认SELinux问题"></a>调试确认SELinux问题</h4><p>为了澄清是否因为SELinux导致的问题，可先执行：</p><p><code>setenforce 0</code> （临时禁用掉SELinux）</p><p><code>getenforce</code>  （得到结果为Permissive）</p><p>如果问题消失了，基本可以确认是SELinux造成的权限问题，需要通过正规的方式来解决权限问题。</p><p>遇到权限问题，在logcat或者kernel的log中一定会打印avc denied提示缺少什么权限，可以通过命令过滤出所有的avc denied，再根据这些log各个击破：</p><p><code>cat /proc/kmsg | grep avc</code></p><p>或</p><p><code>dmesg | grep avc</code></p><p><strong>例如：</strong></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">audit(0.0:67): avc: denied &#123; write &#125; for  path=&quot;/dev/block/vold/93:96&quot; dev=&quot;tmpfs&quot; ino=1263 scontext=u:r:kernel:s0 tcontext=u:object_r:block_device:s0 tclass=blk_file permissive=0</span><br></pre></td></tr></table></figure><p>可以看到有avc denied，且最后有permissive&#x3D;0，表示不允许。</p><h4 id="具体案例分析"><a href="#具体案例分析" class="headerlink" title="具体案例分析"></a>具体案例分析</h4><p><strong>解决原则是：缺什么权限补什么，一步一步补到没有avc denied为止。</strong></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">01-01 00:03:30.889 W/ndroid.settings( 3759): type=1400 audit(0.0:584): avc: denied &#123; search &#125; for name=&quot;dhcp&quot; dev=&quot;mmcblk0p54&quot; ino=49 scontext=u:r:system_app:s0 tcontext=u:object_r:dhcp_data_file:s0 tclass=dir permissive=0</span><br></pre></td></tr></table></figure><p>解决权限问题需要修改的权限文件如下位置，以.te结尾。一般来说需要找到自己项目对应的覆盖文件，不会需要改系统原本的，你可以修改<code>device/qcom/sepolicy/vendor/common/system_app.te</code></p><div class="trm-note info"><div class="trm-note-title">分析过程：</div><ul><li><p>缺少什么权限：   { <strong>search</strong> }权限，</p></li><li><p>谁缺少权限：     scontext&#x3D;u:r:<strong>system_app</strong>:s0</p></li><li><p>对哪个文件缺少权限：tcontext&#x3D;u:object_r:<strong>dhcp_data_file</strong>:s0</p></li><li><p>什么类型的文件：   tclass&#x3D;<strong>dirdir</strong></p></li><li><p>完整的意思： system_app进程对dir类型的dhcp_data_file缺少search权限。</p></li></ul></div><p><strong>解决方法</strong>：修改<code>device/qcom/sepolicy/vendor/common/system_app.te</code>，加入以下内容：</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">allow  system_app   dhcp_data_file:dir  search;</span><br></pre></td></tr></table></figure><p>重新编译，刷机才会生效。</p><p>一般来说在我们添加了文件访问权限除了目录外还有文件也同样需要添加SElinux权限，这个时候我们需要耐心一些，把权限都填上才能保证以后不出现问题，当然经验丰富的人可以提前就添加好多个权限，编译检查是否还有漏加的即可。</p><p>读一个文件的话，需要文件夹的search权限，内部需要读取文件的read getattr open三种权限</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">01-01 00:04:46.009 W/ndroid.settings( 3815): type=1400 audit(0.0:619): avc: denied &#123; read &#125; for name=&quot;dnsmasq.leases&quot; dev=&quot;mmcblk0p54&quot; ino=5760 scontext=u:r:system_app:s0 tcontext=u:object_r:dhcp_data_file:s0 tclass=file permissive=0</span><br></pre></td></tr></table></figure><p>这里再举一个avc报错的例子，然后下面是一条添加多个权限的示例</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">allow  system_app   dhcp_data_file:file  &#123; read getattr open &#125;;</span><br></pre></td></tr></table></figure><p>当然selinux权限还有很多可以研究的，但是目前掌握了这些已经基本够用了。</p><p>Hava a nice day~</p><p>[^1]: 转自<a href="https://www.jianshu.com/p/71acd8dad454" title="《linux：SUID、SGID详解》 崔玉和">linux：SUID、SGID详解</a><br>[^2]: 转自<a href="https://blog.csdn.net/u013553529/article/details/53167072" title="《Android 权限的一些细节》 爱博客大伯">Android权限的一些细节</a><br>[^3]: 转自<a href="https://blog.csdn.net/vshuang/article/details/43639211?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.channel_param" title="《Android安全机制（1）uid，gid与pid》 黄舒颖 咸丫蛋">Android安全机制（1）uid，gid与pid</a><br>[^4]: 转自<a href="https://blog.csdn.net/vshuang/article/details/44001661?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param" title="《Android 权限的一些细节》 黄舒颖 咸丫蛋">Android安全机制（2）AndroidPermission权限控制机制</a><br>[^5]: 转自<a href="https://blog.csdn.net/flykozhang/article/details/50376065" title="《Android 自定义Permission;permission-tree;permission-group自定义（权限，权限组，权限树）》 FlykoZhang">Android自定义Permission;permission-tree;permission-group自定义（权限，权限组，权限树）</a><br>[^6]: 转自<a href="https://blog.csdn.net/a332324956/article/details/17452749?utm_source=blogxgwz0&utm_medium=distribute.pc_relevant.none-task-blog-title-6&spm=1001.2101.3001.4242" title="《android权限代码分析(二)》 明哥的江湖">android权限代码分析(二)</a><br>[^7]: 转自<a href="https://blog.csdn.net/muhuacat/article/details/55259204" title="《android 设置app root权限简单方法》 muhuacat">android 设置app root权限简单方法</a><br>[^8]: 转自<a href="https://blog.csdn.net/tung214/article/details/72734086" title="《Android SELinux avc dennied权限问题解决方法》 缥缈孤鸿影_love">Android SELinuxavcdennied权限问题解决方法</a></p>]]>
    </content>
    <id>
      <![CDATA[https://ainavigation.org/2015/09/29/Android%E7%B3%BB%E7%BB%9Fuid&gid%E6%9D%83%E9%99%90%E4%BB%A5%E5%8F%8Aselinux%E6%9D%83%E9%99%90%E6%B7%BB%E5%8A%A0%E7%9A%84%E7%AE%80%E5%8D%95%E8%A7%A3%E6%9E%90/]]>
    </id>
    <link href="https://ainavigation.org/2015/09/29/Android%E7%B3%BB%E7%BB%9Fuid&amp;gid%E6%9D%83%E9%99%90%E4%BB%A5%E5%8F%8Aselinux%E6%9D%83%E9%99%90%E6%B7%BB%E5%8A%A0%E7%9A%84%E7%AE%80%E5%8D%95%E8%A7%A3%E6%9E%90/"/>
    <published>2015-09-29T04:00:00.000Z</published>
    <summary>
      <![CDATA[<p>在公司做项目发现&#x2F;data&#x2F;misc&#x2F;dhcp这个目录的文件无法访问，抓log后又没有显示selinux权限报错的avcdeny，但是需求又依赖于dhcp协议的解析，所以一直在寻找这个问题的解决方案。现在系统整理一下并把相关知识记录下来，And]]>
    </summary>
    <title>Android系统权限以及selinux权限添加的简单解析</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="Android" scheme="https://ainavigation.org/categories/Android/"/>
    <category term="service" scheme="https://ainavigation.org/tags/service/"/>
    <content>
      <![CDATA[<h1 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h1><p>先自己写个类继承service</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestService</span> <span class="keyword">extends</span> <span class="title class_">Service</span>&#123;</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> IBinder <span class="title function_">onBind</span><span class="params">(Intent intent)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></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"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>service的onBind方法必须重写，如果我们不需要绑定Activity就return null.</p><p>别忘了在<code>AndroidManifest.xml</code>中添加</p><h2 id="service的生命周期"><a href="#service的生命周期" class="headerlink" title="service的生命周期"></a>service的生命周期</h2><p><img src="https://i.loli.net/2020/10/27/zxXN85ULYKt9VyA.png" alt="service的生命周期"  data-tag='post-image' onload='this.onload=null;this.style.opacity=1;' loading="lazy" onerror='this.onerror=null;this.src="/img/404.jpg"'></p><p>每一次组件通过<code>startService(Intent)</code>方法启动service时都会调用<code>onStartCommand(Intent,int,int)</code>两个整型参数，一个是标识符，一个是启动ID。<br>标志股用来区分是一次重新发送，还是一次从没成功过的发送。每次调用调用<code>onStartCommand(Intent,int,int)</code>方法，启动ID都会不同。因此，启动ID<br>也可以用来区别不同的命令。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">onStartCommand</span><span class="params">(Intent intent, <span class="type">int</span> flags, <span class="type">int</span> startId)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line"><span class="keyword">return</span> <span class="built_in">super</span>.onStartCommand(intent, flags, startId);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="non-sticky服务"><a href="#non-sticky服务" class="headerlink" title="non-sticky服务"></a>non-sticky服务</h3><p>IntentService是一种non-sticky服务。non-sticky服务在服务自己认为已经完成任务时停止。为了获得non-sticky服务，应该返回<code>START_NOT_STICKY</code>或<code>START_RELATIVE_INTENT</code>。</p><p>通过调用<code>stopSelf（</code>）或者<code>stopSelf（int）</code>方法，<code>stopSelf（）</code>是一个无条件方法。不管<code>onStartCommand(Intent,int,int)</code>调用多少次，该方法总是能成功停止服务。<br><code>stopSelf（int）</code>该方法需要来自<code>onStartCommand(Intent,int,int)</code>方法的启动ID。只有在接收到最新的启动ID后，该方法才会停止服务。</p><p>如果系统需要在服务完成任务前关闭它，<code>START_NOT_STICKY</code>型的服务会关闭，<code>START_RELATIVE_INTENT</code>型的则会在可用资源不吃紧的时候，尝试再次启动服务。</p><h3 id="sticky服务"><a href="#sticky服务" class="headerlink" title="sticky服务"></a>sticky服务</h3><p>sticky服务会持续运行，直到外部组件调用Context.stopService(Intent)让它停止为止。为启动sticky服务，应返回START_STICKY。</p><h2 id="service的绑定"><a href="#service的绑定" class="headerlink" title="service的绑定"></a>service的绑定</h2><p>在需要绑定的Activity或者fragment中添加这样的代码：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">ServiceConnection</span> <span class="variable">mConn</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServiceConnection</span>() &#123;</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="keyword">void</span> <span class="title function_">onServiceDisconnected</span><span class="params">(ComponentName name)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></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="keyword">void</span> <span class="title function_">onServiceConnected</span><span class="params">(ComponentName name, IBinder service)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">mbinder = (MyBinder) service;</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="type">MyBinder</span> <span class="variable">mbinder</span> <span class="operator">=</span> <span class="literal">null</span>;</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="keyword">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">       </span><br><span class="line">       <span class="type">Intent</span> <span class="variable">i</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Intent</span>(<span class="built_in">this</span>, TestService.class);</span><br><span class="line">       <span class="built_in">this</span>.startService(i);</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">protected</span> <span class="keyword">void</span> <span class="title function_">onDestroy</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line"><span class="built_in">super</span>.onDestroy();</span><br><span class="line">getApplicationContext().unbindService(mConn);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>fragment中注意getActivity的获取</p><p>ServiceConnection是代表服务绑定的一个对象，并负责接收全部绑定回调的方法。</p><p>同时在之前的service中添加：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyBinder</span> <span class="keyword">extends</span> <span class="title class_">Binder</span>&#123;</span><br><span class="line"><span class="keyword">public</span> TestService <span class="title function_">getService</span><span class="params">()</span>&#123;</span><br><span class="line"><span class="keyword">return</span> TestService.<span class="built_in">this</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="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> IBinder <span class="title function_">onBind</span><span class="params">(Intent intent)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MyBinder</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里我们写了getService用于获取当前service的对象，这样我们可以在Activity中通过binder.getService()获取service对象，进而调用service中的public方法。</p><p>绑定服务可能出现的问题</p><blockquote><p>提示不能强制转换，IBinder接口不能转换为Binder类型。</p></blockquote><p>这个问题据说根据堆栈信息的提示就是这个，直接去找也是找到mbinder &#x3D; (MyBinder) service;这一行，目前我看到两种解决方案：</p><blockquote><p>AndroidManifest.xml中标签的过滤器不能是包含包名字符串的，具体原因未知。</p></blockquote><p>此方法未验证</p><blockquote><p>绑定的Activity不是在AndroidManifest.xml中的main启动</p></blockquote><p>由于自身项目需求没有进行尝试，但是从自己写的例子来看，似乎有这种可能</p>]]>
    </content>
    <id>https://ainavigation.org/2015/09/24/Android%20Service%E7%9A%84%E7%AE%80%E5%8D%95%E5%BA%94%E7%94%A8/</id>
    <link href="https://ainavigation.org/2015/09/24/Android%20Service%E7%9A%84%E7%AE%80%E5%8D%95%E5%BA%94%E7%94%A8/"/>
    <published>2015-09-24T04:00:00.000Z</published>
    <summary>
      <![CDATA[<h1 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h1><p>先自己写个类继承service</p>
<figure class="highlight java"]]>
    </summary>
    <title>Android Service的简单应用</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="ALM开发" scheme="https://ainavigation.org/categories/ALM%E5%BC%80%E5%8F%91/"/>
    <category term="redmine" scheme="https://ainavigation.org/tags/redmine/"/>
    <category term="ubuntu" scheme="https://ainavigation.org/tags/ubuntu/"/>
    <content>
      <![CDATA[<h2 id="安装redmine的环境"><a href="#安装redmine的环境" class="headerlink" title="安装redmine的环境"></a>安装redmine的环境</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install -y apache2 php5 libapache2-mod-php5 mysql-server php5-mysql libapache2-mod-perl2 libcurl4-openssl-dev libssl-dev apache2-dev libapr1-dev libaprutil1-dev libmysqlclient-dev libmagickcore-dev libmagickwand-dev curl git-core patch build-essential bison zlib1g-dev libssl-dev libxml2-dev libxml2-dev sqlite3 libsqlite3-dev autotools-dev libxslt1-dev libyaml-0-2 autoconf automake libreadline6-dev libyaml-dev libtool imagemagick apache2-utils vim </span><br></pre></td></tr></table></figure><h2 id="安装ruby"><a href="#安装ruby" class="headerlink" title="安装ruby"></a>安装ruby</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get -y install ruby2.0 ruby2.0-dev ruby1.9.1-dev </span><br></pre></td></tr></table></figure><div class="trm-note tip"><div class="trm-note-title">TIP</div><p>安装过程中数据库的管理员密码为：admin</p></div><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql <span class="operator">-</span>u root <span class="operator">-</span>p</span><br></pre></td></tr></table></figure><h3 id="创建空的数据库"><a href="#创建空的数据库" class="headerlink" title="创建空的数据库"></a>创建空的数据库</h3><figure class="highlight sql"><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"><span class="keyword">CREATE</span> DATABASE redmine <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8;</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="string">&#x27;redmine&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span> IDENTIFIED <span class="keyword">BY</span> <span class="string">&#x27;my_password&#x27;</span>;</span><br><span class="line"><span class="keyword">GRANT</span> <span class="keyword">ALL</span> PRIVILEGES <span class="keyword">ON</span> redmine.<span class="operator">*</span> <span class="keyword">TO</span> <span class="string">&#x27;redmine&#x27;</span>@<span class="string">&#x27;localhost&#x27;</span>;</span><br></pre></td></tr></table></figure><h3 id="更新ruby的库（可能需要多更新几次，尽量更新过吧）"><a href="#更新ruby的库（可能需要多更新几次，尽量更新过吧）" class="headerlink" title="更新ruby的库（可能需要多更新几次，尽量更新过吧）"></a>更新ruby的库（可能需要多更新几次，尽量更新过吧）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem update </span><br></pre></td></tr></table></figure><h2 id="安装budler"><a href="#安装budler" class="headerlink" title="安装budler"></a>安装budler</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem install bundler </span><br></pre></td></tr></table></figure><h2 id="下载redmine2-5-3"><a href="#下载redmine2-5-3" class="headerlink" title="下载redmine2.5.3"></a>下载redmine2.5.3</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget http://www.redmine.org/releases/redmine-2.5.3.tar.gz</span><br></pre></td></tr></table></figure><p>解压<code>redmine-2.5.3.tar.gz</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar zxvf redmine-2.5.3.tar.gz</span><br></pre></td></tr></table></figure><p>进入redmine目录修改配置文件，将其与数据库关联</p><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> redmine-2.5.3/config</span><br><span class="line"><span class="built_in">cp</span> database.yml.examle database.yml</span><br><span class="line">sudo vim databse.yml</span><br></pre></td></tr></table></figure><p><strong>将用户名改为redmine 将密码改为上面对应的my_password</strong></p><h3 id="bundler根据redmine的gemfile进行环境安装"><a href="#bundler根据redmine的gemfile进行环境安装" class="headerlink" title="bundler根据redmine的gemfile进行环境安装"></a>bundler根据redmine的gemfile进行环境安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo bundle install</span><br></pre></td></tr></table></figure><h3 id="生成数据存储秘钥"><a href="#生成数据存储秘钥" class="headerlink" title="生成数据存储秘钥"></a>生成数据存储秘钥</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rake generate_secret_token</span><br></pre></td></tr></table></figure><h3 id="创建数据库结构"><a href="#创建数据库结构" class="headerlink" title="创建数据库结构"></a>创建数据库结构</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RAILS_ENV=production rake db:migrate</span><br></pre></td></tr></table></figure><h3 id="设置默认数据库数据"><a href="#设置默认数据库数据" class="headerlink" title="设置默认数据库数据"></a>设置默认数据库数据</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RAILS_ENV=production rake redmine:load_default_data</span><br></pre></td></tr></table></figure><h3 id="启动redmine服务"><a href="#启动redmine服务" class="headerlink" title="启动redmine服务"></a>启动redmine服务</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ruby script/rails server webrick -e production</span><br></pre></td></tr></table></figure><h3 id="安装插件"><a href="#安装插件" class="headerlink" title="安装插件"></a>安装插件</h3><p>将插件解压或者git下来的文件夹，拷贝到<code>/redmine-2.5.3/plugins/</code>  </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rake redmine:plugins:migrate RAILS_ENV=production</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ainavigation.org/2014/12/15/ubuntu14.04%20%E5%AE%89%E8%A3%85redmine2.5.3/</id>
    <link href="https://ainavigation.org/2014/12/15/ubuntu14.04%20%E5%AE%89%E8%A3%85redmine2.5.3/"/>
    <published>2014-12-15T04:15:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="安装redmine的环境"><a href="#安装redmine的环境" class="headerlink" title="安装redmine的环境"></a>安装redmine的环境</h2><figure class="highlight bash"><t]]>
    </summary>
    <title>ubuntu14.04 安装redmine2.5.3</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>周期的力量</name>
    </author>
    <category term="ALM开发" scheme="https://ainavigation.org/categories/ALM%E5%BC%80%E5%8F%91/"/>
    <category term="LDAP" scheme="https://ainavigation.org/tags/LDAP/"/>
    <category term="redmine" scheme="https://ainavigation.org/tags/redmine/"/>
    <content>
      <![CDATA[<h2 id="安装LDAP"><a href="#安装LDAP" class="headerlink" title="安装LDAP"></a>安装LDAP</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/hosts</span><br></pre></td></tr></table></figure><p>修改 <code>127.0.1.1 hostname.example.com </code>hostname &#x2F;&#x2F;hostname是本机的hostname不是“hostname”</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install slapd ldap-utils //安装ldap</span><br></pre></td></tr></table></figure><p>测试</p><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></pre></td><td class="code"><pre><span class="line">sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config dn</span><br><span class="line"></span><br><span class="line">ldapsearch -x -LLL -H ldap:/// -b dc=example,dc=com dn</span><br></pre></td></tr></table></figure><hr><p>添加用户</p><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></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /etc/ldap</span><br><span class="line"></span><br><span class="line">sudo vim add_content.ldif   //创建add_content.ldif添加下列文字</span><br></pre></td></tr></table></figure><hr><figure class="highlight ldif"><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></pre></td><td class="code"><pre><span class="line"><span class="attribute">dn</span>: ou=People,dc=example,dc=com</span><br><span class="line"></span><br><span class="line"><span class="attribute">objectClass</span>: organizationalUnit</span><br><span class="line"></span><br><span class="line"><span class="attribute">ou</span>: People</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"><span class="attribute">dn</span>: ou=Groups,dc=example,dc=com</span><br><span class="line"></span><br><span class="line"><span class="attribute">objectClass</span>: organizationalUnit</span><br><span class="line"></span><br><span class="line"><span class="attribute">ou</span>: Groups</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"><span class="attribute">dn</span>: cn=miners,ou=Groups,dc=example,dc=com</span><br><span class="line"></span><br><span class="line"><span class="attribute">objectClass</span>: posixGroup</span><br><span class="line"></span><br><span class="line"><span class="attribute">cn</span>: miners</span><br><span class="line"></span><br><span class="line"><span class="attribute">gidNumber</span>: 5000</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"></span><br><span class="line"><span class="attribute">dn</span>: uid=john,ou=People,dc=example,dc=com</span><br><span class="line"></span><br><span class="line"><span class="attribute">objectClass</span>: inetOrgPerson</span><br><span class="line"></span><br><span class="line"><span class="attribute">objectClass</span>: posixAccount</span><br><span class="line"></span><br><span class="line"><span class="attribute">objectClass</span>: shadowAccount</span><br><span class="line"></span><br><span class="line"><span class="attribute">uid</span>: john</span><br><span class="line"></span><br><span class="line"><span class="attribute">sn</span>: Doe</span><br><span class="line"></span><br><span class="line"><span class="attribute">givenName</span>: John</span><br><span class="line"></span><br><span class="line"><span class="attribute">cn</span>: John Doe</span><br><span class="line"></span><br><span class="line"><span class="attribute">mail</span>: 385691567@qq.com</span><br><span class="line"></span><br><span class="line"><span class="attribute">displayName</span>: John Doe</span><br><span class="line"></span><br><span class="line"><span class="attribute">uidNumber</span>: 10000</span><br><span class="line"></span><br><span class="line"><span class="attribute">gidNumber</span>: 5000</span><br><span class="line"></span><br><span class="line"><span class="attribute">userPassword</span>: johnldap</span><br><span class="line"></span><br><span class="line"><span class="attribute">gecos</span>: John Doe</span><br><span class="line"></span><br><span class="line"><span class="attribute">loginShell</span>: /bin/bash</span><br><span class="line"></span><br><span class="line"><span class="attribute">homeDirectory</span>: /home/john</span><br></pre></td></tr></table></figure><hr><figure class="highlight ldif"><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">ldapadd -x -D cn=admin,dc=example,dc=com -W -f add_content.ldif //添加ldif文件</span><br><span class="line"></span><br><span class="line">ldapsearch -x -LLL -b dc=example,dc=com &#x27;uid=john&#x27; cn gidNumber //查询john</span><br></pre></td></tr></table></figure><div class="trm-note tip"><div class="trm-note-title">TIP</div><p>-x:“简单”绑定;不使用默认SASL方法</p><p>-LLL:禁用打印无关的信息</p><p>uid &#x3D;john:一个“过滤器”找到john用户</p><p>cn gidNumber:请求某些特定属性的显示(默认是显示所有属性)</p></div><h2 id="redmine设置"><a href="#redmine设置" class="headerlink" title="redmine设置"></a>redmine设置</h2><hr><p>认证模式（LDAP）</p><p>名称：（随便填）</p><p>主机： ldap服务器IP</p><p>端口： 默认389</p><p>账号： cn&#x3D;admin,dc&#x3D;example,dc&#x3D;com</p><p>密码： admin</p><p>Base DN: dc&#x3D;example,dc&#x3D;com</p><p>LDAP过滤器：</p><p>超时（秒）：</p><p>即时用户生成：打勾</p><p>登录名属性：uid</p><p>名字属性：sn</p><p>姓氏属性：givenName</p><p>邮件属性：mail</p><hr>]]>
    </content>
    <id>https://ainavigation.org/2014/12/09/ldap%E5%AE%89%E8%A3%85%E4%B8%8E%E5%85%B3%E8%81%94redmine/</id>
    <link href="https://ainavigation.org/2014/12/09/ldap%E5%AE%89%E8%A3%85%E4%B8%8E%E5%85%B3%E8%81%94redmine/"/>
    <published>2014-12-09T04:09:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="安装LDAP"><a href="#安装LDAP" class="headerlink" title="安装LDAP"></a>安装LDAP</h2><figure class="highlight bash"><table><tr><td class="gutt]]>
    </summary>
    <title>LDAP安装与关联redmine</title>
    <updated>2026-04-05T02:25:26.011Z</updated>
  </entry>
</feed>
