微信小遊戲直播在Android端的跨進程渲染推流實踐

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文由微信開發團隊工程師“virwu”分享。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1、引言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"近期,微信小遊戲支持了視頻號一鍵開播,將微信升級到最新版本,打開騰訊系小遊戲(如跳一跳、歡樂鬥地主等),在右上角菜單就可以看到發起直播的按鈕一鍵成爲遊戲主播了(如下圖所示)。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c8523638f5d4580649b451cd9f6929de.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然而微信小遊戲出於性能和安全等一系列考慮,運行在一個獨立的進程中,在該環境中不會初始化視頻號直播相關的模塊。這就意味着小遊戲的音視頻數據必須跨進程傳輸到主進程進行推流,給我們實現小遊戲直播帶來了一系列挑戰。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(本文同步發佈於:","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3594-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"http://www.52im.net/thread-3594-1-1.html","attrs":{}}]},{"type":"text","text":")","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2、系列文章","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文是系列文章中的第5篇:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1236-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(一):百萬在線的美拍直播彈幕系統的實時推送技術實踐之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3252-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(二):阿里電商IM消息平臺,在羣聊、直播場景下的技術實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3376-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(三):微信直播聊天室單房間1500萬在線的消息架構演進之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3515-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(四):百度直播的海量用戶實時消息系統架構演進實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3594-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(五):微信小遊戲直播在Android端的跨進程渲染推流實踐","attrs":{}}]},{"type":"text","text":"》(","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"* 本文","attrs":{}},{"type":"text","text":")","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3、視頻採集推流","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1 錄屏採集?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小遊戲直播本質上就是把主播手機屏幕上的內容展示給觀衆,自然而然地我們可以想到採用系統的錄屏接口MediaProjection進行視頻數據的採集。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這種方案有這些優點:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)系統接口,實現簡單,兼容性和穩定性有一定保證;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)後期可以擴展成通用的錄屏直播;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)對遊戲性能影響較小,經測試對幀率影響在10%以內;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)可以直接在主進程進行數據處理及推流,不用處理小遊戲跨進程的問題。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"但是最終這個方案被否決了,主要出於以下考慮:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)需要展示系統授權彈窗;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)需要謹慎處理切出小遊戲後暫停畫面推流的情況,否則可能錄製到主播的其他界面,有隱私風險;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)最關鍵的一點:產品設計上需要在小遊戲上展示一個評論掛件(如下圖),便於主播查看直播評論以及進行互動,錄屏直播會讓觀衆也看到這個組件,影響觀看體驗的同時會暴露一些只有主播才能看到的數據。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c1/c10720b54a27b5d13fad810bb2cdc18c.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8b/8bacf0ee289ea916794884ed73f7aaf3.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉念一想,既然小遊戲的渲染完全是由我們控制的,爲了更好的直播體驗,能否將小遊戲渲染的內容跨進程傳輸到主進程來進行推流呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2 小遊戲渲染架構","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了更好地描述我們採用的方案,這裏先簡單介紹一下小遊戲的渲染架構:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3e/3e22037d162ca436f0b588263f7cf5f2.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到圖中左半邊表示在前臺的小遊戲進程,其中MagicBrush爲小遊戲渲染引擎,它接收來自於小遊戲代碼的渲染指令調用,將畫面渲染到在屏的SurfaceView提供的Surface上。整個過程主進程在後臺不參與。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3 小遊戲錄屏時的情況","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小遊戲之前支持過遊戲內容的錄製,和直播原理上類似,都需要獲取當前小遊戲的畫面內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"錄屏啓用時小遊戲會切換到如下的模式進行渲染:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/06/06f55171034ab8840826fd1f73361afa.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,MagicBrush的輸出目標不再是在屏的SurfaceView,而是Renderer產生的一個SurfaceTexture。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這裏先介紹一下Renderer的作用:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Renderer是一個獨立的渲染模塊,表示一個獨立的GL環境,它可以創建SurfaceTexture作爲輸入,收到SurfaceTexture的onFrameAvailable回調後通過updateTexImage方法將圖像數據轉換爲類型是GL_TEXTURE_EXTERNAL_OES的紋理參與後續的渲染過程,並可以將渲染結果輸出到另一個Surface上。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"下面逐步對圖中過程進行解釋:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"1)","attrs":{}},{"type":"text","text":"MagicBrush接收來自小遊戲代碼的渲染指令調用,將小遊戲內容渲染到第一個Renderer所創建的SurfaceTexture上;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"2)","attrs":{}},{"type":"text","text":"隨後這個Renderer做了兩件事情:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.1)將得到的小遊戲畫面紋理再次渲染到了在屏的Surface上;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.2)提供紋理ID給到第二個Renderer(這裏兩個Renderer通過共享GLContext來實現共享紋理)。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"strong","attrs":{}}],"text":"3)","attrs":{}},{"type":"text","text":"第二個Renderer將第一個Renderer提供的紋理渲染到mp4編碼器提供的輸入SurfaceTexture上,最終編碼器編碼產生mp4錄屏文件。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.4 改造錄屏方案?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,錄屏方案中通過一個Renderer負責將遊戲內容上屏,另一個Renderer將同樣的紋理渲染到編碼器上的方式實現了錄製遊戲內容,直播其實類似,是不是隻要將編碼器替換爲直播的推流模塊就可以了呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"確實如此,但還缺少關鍵的一環:","attrs":{}},{"type":"text","text":"推流模塊運行在主進程,我們需要實現跨進程傳輸圖像數據!如何跨進程呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"說到跨進程:","attrs":{}},{"type":"text","text":"可能我們腦海裏蹦出的第一反應就是Binder、Socket、共享內存等等傳統的IPC通信方法。但仔細一想,系統提供的SurfaceView是非常特殊的一個View組件,它不經過傳統的View樹來參與繪製,而是直接經由系統的SurfaceFlinger來合成到屏幕上,而SurfaceFlinger運行在系統進程上,我們繪製在SurfaceView所提供的Surface上的內容必然是可以跨進程進行傳輸的,而Surface跨進程的方法很簡單——它本身就實現了Parcelable接口,這意味着我們可以用Binder直接跨進程傳輸Surface對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"於是我們有了下面這個初步方案:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/76/7640603166bae57e627f6ab0de1b18ca.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"可以看到:","attrs":{}},{"type":"text","text":"第3步不再是渲染到mp4編碼器上,而是渲染到主進程跨進程傳輸過來的Surface上,主進程的這個Surface是通過一個Renderer創建的SurfaceTexture包裝而來的,現在小遊戲進程作爲生產者向這個Surface渲染畫面。當一幀渲染完畢後,主進程的SurfaceTexture就會收到onFrameAvailable回調通知圖像數據已經準備完畢,隨之通過updateTexImage獲取到對應的紋理數據,這裏由於直播推流模塊只支持GL_TEXTURE_2D類型的紋理,這裏主進程Renderer會將GL_TEXTURE_EXTERNAL_OES轉換爲GL_TEXTURE_2D紋理後給到直播推流編碼器,完成推流過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"經過一番改造:","attrs":{}},{"type":"text","text":"上述方案成功地實現了將小遊戲渲染在屏幕上的同時傳遞給主進程進行推流,但這真的是最優的方案嗎?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"思考一番,發現上述方案中的Renderer過多了,小遊戲進程中存在兩個,一個用於渲染上屏,一個用於渲染到跨進程而來的Surface上,主進程中還存在一個用於轉換紋理以及調用推流模塊。如果要同時支持錄屏,還需要在小遊戲進程再起一個Renderer用於渲染到mp4編碼器,過多的Renderer意味着過多的額外渲染開銷,會影響小遊戲運行性能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.5 跨進程渲染方案","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"縱觀整個流程,其實只有主進程的Renderer是必要的,小遊戲所使用的額外Render無非就是想同時滿足渲染上屏和跨進程傳輸,讓我們大開腦洞——既然Surface本身就不受進程的約束,那我們乾脆把小遊戲進程的在屏Surface傳遞到主進程進行渲染上屏吧!","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ec/ec7bfd49c01c047e8d2c55fc8426a332.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終我們大刀闊斧地砍掉了小遊戲進程的兩個冗餘Renderer,MagicBrush直接渲染到了跨進程傳遞而來的Surface上,而主進程的Renderer在負責紋理類型轉換的同時也負責將紋理渲染到跨進程傳遞而來的小遊戲進程的在屏Surface上,實現畫面的渲染上屏。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終所需要的Renderer數量從原來的3個減少到了必要的1個,在架構更加清晰的同時提升了性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後續需要同時支持錄屏時,只要稍作改動,將mp4編碼器的輸入SurfaceTexture也跨進程傳遞到主進程,再新增一個Renderer渲染紋理到它上面就行了(如下圖所示)。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e4/e4be4a89595036717c13b4b0fbfe9c91.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.6 兼容性與性能","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏,不禁有點擔心,跨進程傳輸和渲染Surface的這套方案的兼容性會不會有問題呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"實際上,雖然並不常見,但是官方文檔裏面是有說明可以跨進程進行繪製的:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SurfaceView combines a surface and a view. SurfaceView's view components are composited by SurfaceFlinger (and not the app), enabling rendering from a separate thread/process and isolation from app UI rendering.","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"並且Chrome以及Android O以後的系統WebView都有使用跨進程渲染的方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我們的兼容性測試中,覆蓋了Android 5.1及以後的各個主流系統版本和機型,除了Android 5.x機型上出現了跨進程渲染黑屏的問題外,其餘均可以正常渲染上屏和推流。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"性能方面:","attrs":{}},{"type":"text","text":"我們使用了WebGL水族館的Demo進行了性能測試,可以看到對於平均幀率的影響在15%左右,主進程的CPU因爲渲染和推流有所升高,奇怪的是小遊戲進程的CPU開銷卻出現了一些下降,這裏下降的原因暫時還沒有確認,懷疑與上屏操作移到主進程相關,也不排除是統計方法的影響。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c5/c58312410317c1d4cf058a074b6f869b.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.7 小結一下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了實現不錄製主播端的評論掛件,我們從小遊戲渲染流程入手,藉助於Surface跨進程渲染和傳輸圖像的能力,把小遊戲渲染上屏的過程移到了主進程,並同時生成紋理進行推流,在兼容性和性能上達到了要求。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4、音頻採集推流","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.1 方案選擇","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在音頻採集方案中,我們注意到在Android 10及以上系統提供了AudioPlaybackCapture方案允許我們在一定的限制內對系統音頻進行採集。當時預研的一些結論如下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"捕獲方 - 進行捕獲的條件:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)Android 10(api 29)及以上;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)獲取了RECORD_AUDIO權限;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)通過MediaProjectionManager.createScreenCaptureIntent()進行MediaProjection權限的申請(和MediaProjection錄屏共用);","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)通過AudioPlaybackCaptureConfiguration.addMatchingUsage()/AudioPlaybackCaptureConfiguration.excludeUsage() 添加/排除要捕獲的MEDIA類型;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5)通過 AudioPlaybackCaptureConfiguration.addMatchingUid() /AudioPlaybackCaptureConfiguration.excludeUid()添加/排除可以捕獲的應用的UID。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"被捕獲方 - 可以被捕獲的條件:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)Player的AudioAttributes設置的Usage爲USAGE_UNKNOWN,USAGE_GAME或USAGE_MEDIA(目前絕大部分用的都是默認值,可以被捕獲);","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)應用的CapturePolicy被設置爲AudioAttributes#ALLOW_CAPTURE_BY_ALL,有三種辦法可以設置(以最嚴格的爲準,目前微信內沒有配置,默認爲可以捕獲);","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)通過manifest.xml設置android:allowAudioPlaybackCapture=\"true\",其中,TargetApi爲29及以上的應用默認爲true,否則爲false;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)api 29及以上可以通過setAllowedCapturePolicy方法運行時設置;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5)api 29及以上可以通過AudioAttributes針對每一個Player單獨設置。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"總的來說:","attrs":{}},{"type":"text","text":"Android 10及以上可以使用AudioPlaybackCapture方案進行音頻捕獲,但考慮到Android 10這個系統版本限制過高,最終我們選擇了自己來採集並混合小遊戲內播放的所有音頻。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.2 跨進程音頻數據傳輸","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,老問題又擺在了我們眼前:小遊戲混合後的音頻數據在小遊戲進程,而我們需要把數據傳輸到主進程進行推流。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與一般的IPC跨進程通信用於方法調用不同:在這個場景下,我們需要頻繁地(40毫秒一次)傳輸較大的數據塊(16毫秒內的數據量在8k左右)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"同時:","attrs":{}},{"type":"text","text":"由於直播的特性,這個跨進程傳輸過程的延遲需要儘可能地低,否則就會出現音畫不同步的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了達到上述目標:","attrs":{}},{"type":"text","text":"我們對Binder、LocalSocket、MMKV、SharedMemory、Pipe這幾種IPC方案進行了測試。在搭建的測試環境中,我們在小遊戲進程模擬真實的音頻傳輸的過程,每隔16毫秒發送一次序列化後的數據對象,數據對象大小分爲3k/4M/10M三擋,在發送前儲存時間戳在對象中;在主進程中接收到數據並完成反序列化爲數據對象的時刻作爲結束時間,計算傳輸延遲。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最終得到了如下結果:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2d/2d47423deaf3aad78afef81dbb0c7b39.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注:","attrs":{}},{"type":"text","text":"其中XIPCInvoker(Binder)和MMKV在傳輸較大數據量時耗時過長,不在結果中展示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對於各個方案的分析如下(卡頓率表示延遲>2倍平均延遲且>10毫秒的數據佔總數的比例):","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab329f09567cdf144e5674059564670b.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"可以看到:","attrs":{}},{"type":"text","text":"LocalSocket方案在各個情況下的傳輸延遲表現都極爲優異。差異的原因主要是因爲裸二進制數據在跨進程傳輸到主進程後,仍需要進行一次數據拷貝操作來反序列化爲數據對象,而使用LocalSocket時可以藉助於ObjectStream和Serializeable來實現流式的拷貝,相比與其他方案的一次性接收數據後再拷貝節約了大量的時間(當然其他方案也可以設計成分塊流式傳輸同時拷貝,但實現起來有一定成本,不如ObjectStream穩定易用)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們也對LocalSocket進行了兼容性與性能測試,未出現不能傳輸或斷開連接的情況,僅在三星S6上平均延遲超過了10毫秒,其餘機型延遲均在1毫秒左右,可以滿足我們的預期。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.3 LocalSocket的安全性","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"常用的Binder的跨進程安全性有系統實現的鑑權機制來保證,LocalSocket作爲Unix domain socket的封裝,我們必須考慮它的安全性問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"論文《","attrs":{}},{"type":"link","attrs":{"href":"https://links.jianshu.com/go?to=https%3A//dblp.org/rec/conf/ccs/ShaoOJQM16.html","title":null,"type":null},"content":[{"type":"text","text":"The Misuse of Android Unix Domain Sockets and Security Implications","attrs":{}}]},{"type":"text","text":"》較爲詳細地分析了Android中使用LocalSocket所帶來的安全風險。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"PS:","attrs":{}},{"type":"text","text":"論文原文附件下載(請從此鏈接的4.3節處下載:","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3594-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"http://www.52im.net/thread-3594-1-1.html","attrs":{}}]},{"type":"text","text":")","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"總結論文所述:","attrs":{}},{"type":"text","text":"由於LocalSocket本身缺乏鑑權機制,任意一個應用都可以進行連接,從而截取到數據或是向接收端傳遞非法數據引發異常。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"針對這個特點,我們可以做的防禦方法有兩種:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)隨機化LocalSocket的命名,例如使用當前直播的小遊戲的AppId和用戶uin等信息計算md5作爲LocalSocket的名字,使得攻擊者無法通過固定或窮舉名字的方法嘗試建立連接;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)引入鑑權機制,在連接成功後發送特定的隨機信息來驗證對方的真實性,然後才啓動真正的數據傳輸。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.4 小結一下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了兼容Android 10以下的機型也能直播,我們選擇自己處理小遊戲音頻的採集,並通過對比評測,選用了LocalSocket作爲跨進程音頻數據傳輸的方案,在延遲上滿足了直播的需求。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時,通過一些對抗措施,可以有效規避LocalSocket的安全風險。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"5、多進程帶來的問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回頭來看,雖然整個方案看起來比較通順,但是在實現的過程中還是由於多進程的原因踩過不少坑,下面就分享其中兩個比較主要的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"5.1 glFinish造成渲染推流幀率嚴重下降","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在剛實現跨進程渲染推流的方案後,我們進行了一輪性能與兼容性測試,在測試中發現,部分中低端機型上幀率下降非常嚴重(如下圖所示)。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f0/f089fdabe855838c9f57c8c92c41c1c3.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"復現後查看小遊戲進程渲染的幀率(即小遊戲進程繪製到跨進程而來的Surface上的幀率)發現可以達到不開直播時的幀率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而我們所用的測試軟件PerfDog所記錄的是在屏Surface的繪製幀率,這就說明性能下降不是直播開銷過高引起的小遊戲代碼執行效率下降,而是主進程上屏Renderer效率太低。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"於是我們對主進程直播時運行效率進行了Profile,發現耗時函數爲glFinish。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"並且有兩次調用:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)第一次調用是Renderer將外部紋理轉2D紋理時,耗時會達到100多毫秒;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)第二次調用是騰訊雲直播SDK內部,耗時10毫秒以內。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果將第一次調用去掉,直播SDK內部的這次則會耗時100多毫秒。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了弄清爲什麼這個GL指令耗時這麼久,我們先看看它的描述:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"glFinish does not return until the effects of all previously called GL commands are complete.","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"描述很簡單:","attrs":{}},{"type":"text","text":"它會阻塞直到之前調用的所有GL指令全部完成。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這麼看來是之前的GL指令太多了?","attrs":{}},{"type":"text","text":"但是GL指令隊列是以線程爲維度隔離的,在主進程的Renderer線程中,glFinish前只會執行紋理類型轉換的非常少量的GL指令,和騰訊雲的同學瞭解到推流接口也不會在本線程執行很多GL指令,如此少量的GL指令怎麼會使glFinish阻塞這麼久呢?等等,大量GL指令?小遊戲進程此時不就正在執行大量GL指令嗎,難道是小遊戲進程的大量GL指令導致了主進程的glFinsih耗時過長?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這樣的猜測不無道理:","attrs":{}},{"type":"text","text":"雖然GL指令隊列是按線程隔離的,但處理指令的GPU只有一個,一個進程的GL指令過多導致另一個進程在需要glFinish時阻塞過久。Google了一圈沒找到有相關的描述,需要自己驗證這個猜測。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重新觀察一遍上面的測試數據:","attrs":{}},{"type":"text","text":"發現直播前能達到60幀的情況下,直播後也能達到60幀左右,這是不是就說明在小遊戲的GPU負載較低時glFinish的耗時也會下降呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在性能下降嚴重的機型上:","attrs":{}},{"type":"text","text":"控制其他變量不變嘗試運行低負載的小遊戲,發現glFinsih的耗時成功下降到了10毫秒左右,這就印證了上面的猜測——確實是小遊戲進程正在執行的大量GL指令阻塞了主進程glFinish的執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該如何解決呢?小遊戲進程的高負載無法改變,那能讓小遊戲在一幀渲染完成以後停住等主進程的glFinish完成後再渲染下一幀嗎?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"這裏經過了各種嘗試:","attrs":{}},{"type":"text","text":"OpenGL的glFence同步機制無法跨進程使用;由於GL指令是異步執行的,通過跨進程通信加鎖鎖住小遊戲的GL線程並不能保證主進程執行glFinish時小遊戲進程的指令已經執行完,而能保證這點只有通過給小遊戲進程加上glFinish,但這會使得雙緩衝機制失效,導致小遊戲渲染幀率的大幅下降。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然glFinish所帶來的阻塞無法避免,那我們回到問題的開始:爲什麼需要glFinish?由於雙緩衝機制的存在,一般來說並不需要glFinish來等待之前的繪製完成,否則雙緩衝就失去了意義。兩次glFinish中,第一次紋理處理的調用可以直接去掉,第二次騰訊雲SDK的調用經過溝通,發現是爲了解決一個歷史問題引入的,可以嘗試去掉。在騰訊雲同學的幫助下,去掉glFinish後,渲染的幀率終於和小遊戲輸出的幀率一致,經過兼容性和性能測試,沒有發現去掉glFinish帶來的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個問題最終的解法很簡單:但分析問題原因的過程實際上做了非常多的實驗,同一個應用中一個高GPU負載的進程會影響到另一個進程的glFinish耗時的這種場景確實也非常少見,能參考的資料不多。這個過程也讓我深刻體會到了glFinish使得雙緩衝機制失效所帶來的性能影響是巨大的,在使用OpenGL進行渲染繪製時對於glFinish的使用應當非常謹慎。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"5.2 後臺進程優先級問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在測試過程中:我們發現無論以多少的幀率向直播SDK發送畫面,觀衆端看到的畫面幀率始終只有16幀左右,排除後臺原因後,發現是編碼器編碼的幀率不足導致的。經騰訊雲同學測試同進程內編碼的幀率是可以達到設定的30幀的,那麼說明還是多進程帶來的問題,這裏編碼是一個非常重的操作,需要消耗比較多的CPU資源,所以我們首先懷疑的就是後臺進程優先級的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"爲了確認問題:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)我們找來了已經root的手機,通過chrt命令提高編碼線程的優先級,觀衆端幀率立馬上到了25幀;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)另一方面,經測試如果在小遊戲進程上顯示一個主進程的浮窗(使主進程具有前臺優先級),幀率可以上到30幀。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"綜上:","attrs":{}},{"type":"text","text":"可以確認幀率下降就是由於後臺進程(以及其擁有的線程)的優先級過低導致的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提高線程優先級的做法在微信裏比較常見,例如:小程序的JS線程以及小遊戲的渲染線程都會在運行時通過","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"android.os.Process.setThreadPriority","attrs":{}},{"type":"text","text":"方法設置線程的優先級。騰訊雲SDK的同學很快提供了接口供我們設置線程優先級,但當我們真正運行起來時,卻發現編碼的幀率僅從16幀提高到了18幀左右,是哪裏出問題了呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"前面提到:","attrs":{}},{"type":"text","text":"我們通過chrt命令設置線程優先級是有效的,但android.os.Process.setThreadPriority這個方法設置的線程優先級對應的是renice這個命令設置的nice值。仔細閱讀chrt的manual後,發現之前測試時的理解有誤,之前直接用chrt -p [pid] [priority]的命令設置優先級,卻沒有設置調度策略這個參數,導致該線程的調度策略從Linux默認的SCHED_OTHER改爲了命令缺省設置的SCHED_RR,而SCHED_RR是一種“實時策略”,導致線程的調度優先級變得非常高。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"實際上:","attrs":{}},{"type":"text","text":"通過renice(也就是android.os.Process.setThreadPriority)設置的線程優先級,對於後臺進程所擁有線程來說沒有太大的幫助。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"其實早有人解釋過這一點:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"To address this, Android also uses Linux cgroups in a simple way to create more strict foreground vs. background scheduling. The foreground/default cgroup allows thread scheduling as normal. The background cgroup however applies a limit of only some small percent of the total CPU time being available to all threads in that cgroup. Thus if that percentage is 5% and you have 10 background threads all wanting to run and one foreground thread, the 10 background threads together can only take at most 5% of the available CPU cycles from the foreground. (Of course if no foreground thread wants to run, the background threads can use all of the available CPU cycles.)","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於線程優先級的設置,感興趣的同學可以看看另一位大佬的文章:《","attrs":{}},{"type":"link","attrs":{"href":"https://links.jianshu.com/go?to=https%3A//mp.weixin.qq.com/s/oLz_F7zhUN6-b-KaI8CMRw","title":null,"type":null},"content":[{"type":"text","text":"Android的離奇陷阱 — 設置線程優先級導致的微信卡頓慘案","attrs":{}}]},{"type":"text","text":"》。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最終:","attrs":{}},{"type":"text","text":"爲了提高編碼幀率並防止後臺主進程被殺,我們最終還是決定直播時在主進程創建一個前臺Service。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"6、總結與展望","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多進程是一把雙刃劍,在給我們帶來隔離性和性能優勢的同時也帶來了跨進程通信這一難題,所幸藉助系統Surface的能力和多種多樣的跨進程方案可以較好地解決小遊戲直播中所遇到的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"當然:","attrs":{}},{"type":"text","text":"解決跨進程問題最好的方案是避免跨進程,我們也考慮了將視頻號直播的推流模塊運行在小遊戲進程的方案,但出於改造成本的考慮而沒有選擇這一方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"同時:","attrs":{}},{"type":"text","text":"這次對於SurfaceView跨進程渲染的實踐也對其他業務有一定參考價值——對於一些內存壓力較大或是安全風險較高,又需要進行SurfaceView渲染繪製的場景,可以把邏輯放到獨立的進程,再通過跨進程渲染的方式繪製到主進程的View上,在獲得獨立進程優勢的同時又避免了進程間跳轉所帶來的體驗的割裂。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"附錄1:有關直播技術的文章彙總","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-853-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時音視頻直播技術詳解(一):開篇","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-955-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時音視頻直播技術詳解(二):採集","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-960-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時音視頻直播技術詳解(三):處理","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-965-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時音視頻直播技術詳解(四):編碼和封裝","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-967-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時音視頻直播技術詳解(五):推流和傳輸","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-972-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時音視頻直播技術詳解(六):延遲優化","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-875-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"理論聯繫實際:實現一個簡單地基於html]5的實時視頻直播","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1564-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"實時視頻直播客戶端技術盤點:Native、html]5、WebRTC、微信小程序","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1154-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"Android直播入門實踐:動手搭建一套簡單的直播系統","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3220-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"淘寶直播技術乾貨:高清、低延時的實時視頻直播技術解密","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2087-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"技術乾貨:實時視頻直播首屏耗時400ms內的優化實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2022-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"新浪微博技術分享:微博實時直播答題的百萬高併發架構實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1904-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"實時音頻的混音在視頻直播中的技術原理和實踐總結","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1406-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"七牛雲技術分享:使用QUIC協議實現實時視頻直播0卡頓!","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1369-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"近期大熱的實時直播答題系統的實現思路與技術難點分享","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1289-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"P2P技術如何將實時視頻直播帶寬降低75%?","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1254-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"網易雲信實時視頻直播在TCP數據傳輸層的一些優化思路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1033-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"首次披露:快手是如何做到百萬觀衆同場看直播仍能秒開且不卡頓的?","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-953-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"淺談實時音視頻直播中直接影響用戶體驗的幾項關鍵技術指標","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-541-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"技術揭祕:支持百萬級粉絲互動的Facebook實時視頻直播","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-530-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端實時視頻直播技術實踐:如何做到實時秒開、流暢不卡","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-528-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"實現延遲低於500毫秒的1080P實時音視頻直播的實踐分享","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-475-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"淺談開發實時視頻直播平臺的技術要點","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1236-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(一):百萬在線的美拍直播彈幕系統的實時推送技術實踐之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3252-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(二)阿里電商IM消息平臺,在羣聊、直播場景下的技術實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3376-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(三):微信直播聊天室單房間1500萬在線的消息架構演進之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3515-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(四):百度直播的海量用戶實時消息系統架構演進實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3594-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"直播系統聊天技術(五):微信小遊戲直播在Android端的跨進程渲染推流實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1562-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"海量實時消息的視頻直播系統架構演進之路(視頻+PPT)[附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1379-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"YY直播在移動弱網環境下的深度優化實踐分享(視頻+PPT)[附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-213-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"從0到1:萬人在線的實時音視頻直播技術實踐分享(視頻+PPT) [附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-196-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"在線音視頻直播室服務端架構最佳實踐(視頻+PPT) [附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">> ","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/forum.php%3Fmod%3Dcollection%26action%3Dview%26ctid%3D29","title":null,"type":null},"content":[{"type":"text","text":"更多同類文章 ……","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"附錄2:微信團隊分享的技術文章彙總","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1569-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信朋友圈千億訪問量背後的技術挑戰和實踐總結","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1545-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:微信移動端的全文檢索多音字問題解決方案","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1461-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:iOS版微信的高性能通用key-value組件技術實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1449-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:iOS版微信是如何防止特殊字符導致的炸羣、APP崩潰的?","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1422-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:iOS版微信的內存監控系統技術實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1404-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"iOS後臺喚醒實戰:微信收款到賬語音提醒技術總結","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1377-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:視頻圖像的超分辨率技術原理和應用場景","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1311-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:微信每日億次實時音視頻聊天背後的技術解密","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1173-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:微信Android版小視頻編碼填過的那些坑","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1132-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信手機端的本地數據全文檢索優化之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1131-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"企業微信客戶端中組織架構數據的同步更新方案優化實戰","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1099-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊披露:微信界面卡死超級bug“15。。。。”的來龍去脈","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1086-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"月活8.89億的超級IM微信是如何進行Android端兼容測試的","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-932-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"一篇文章get微信開源移動端數據庫組件WCDB的一切!","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-921-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信客戶端團隊負責人技術訪談:如何着手客戶端性能監控和優化","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-895-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信後臺基於時間序的海量數據冷熱分級架構設計實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-893-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:Android版微信的臃腫之困與模塊化實踐之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-801-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信後臺團隊:微信後臺異步消息隊列的優化升級實踐分享","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-789-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:微信客戶端SQLite數據庫損壞修復實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-620-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信Mars:微信內部正在使用的網絡層封裝庫,即將開源","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-684-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"如約而至:微信自用的移動端IM網絡層跨平臺組件庫Mars已正式開源","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-623-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"開源libco庫:單機千萬連接、支撐微信8億用戶的後臺框架基石 [源碼下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-310-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信新一代通信安全解決方案:基於TLS1.3的MMTLS詳解","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-210-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:Android版微信後臺保活實戰分享(進程保活篇)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-209-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:Android版微信後臺保活實戰分享(網絡保活篇)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-206-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"Android版微信從300KB到30MB的技術演進(PPT講稿) [附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-205-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:Android版微信從300KB到30MB的技術演進","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-200-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信技術總監談架構:微信之道——大道至簡(演講全文)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-199-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信技術總監談架構:微信之道——大道至簡(PPT講稿) [附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-201-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"如何解讀《微信技術總監談架構:微信之道——大道至簡》","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-186-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信海量用戶背後的後臺系統存儲架構(視頻+PPT) [附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-624-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信異步化改造實踐:8億月活、單機千萬連接背後的後臺解決方案","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-178-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信朋友圈海量技術之道PPT [附件下載]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-195-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信對網絡影響的技術試驗及分析(論文全文)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-179-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"一份微信後臺技術架構的總結性筆記","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-177-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"架構之道:3個程序員成就微信朋友圈日均10億發佈量[有視頻]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-168-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-170-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"快速裂變:見證微信強大後臺架構從0到1的演進歷程(二)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-143-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創分享:Android內存泄漏監控和優化技巧總結","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-142-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"全面總結iOS版微信升級iOS9遇到的各種“坑”","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-139-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創資源混淆工具:讓你的APK立減1M","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-140-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊原創Android資源混淆工具:AndResGuard [有源碼]","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-138-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"Android版微信安裝包“減肥”實戰記錄","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-137-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"iOS版微信安裝包“減肥”實戰記錄","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-136-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:iOS版微信界面卡頓監測方案","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-128-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信“紅包照片”背後的技術難題","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-126-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:iOS版微信小視頻功能技術方案實錄","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-124-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:Android版微信如何大幅提升交互性能(一)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-125-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:Android版微信如何大幅提升交互性能(二)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-120-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:實現Android版微信的智能心跳機制","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-121-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:WhatsApp、Line、微信的心跳策略分析","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-122-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:谷歌消息推送服務(GCM)研究(來自微信)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-116-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"移動端IM實踐:iOS版微信的多設備字體適配方案探討","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1605-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"IPv6技術詳解:基本概念、應用現狀、技術實踐(上篇)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1607-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"IPv6技術詳解:基本概念、應用現狀、技術實踐(下篇)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1746-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信多媒體團隊訪談:音視頻開發的學習、微信的音視頻技術和挑戰等","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1799-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"騰訊技術分享:微信小程序音視頻技術背後的故事","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1811-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1828-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信多媒體團隊梁俊斌訪談:聊一聊我所瞭解的音視頻技術","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1308-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"騰訊音視頻實驗室:使用AI黑科技實現超低碼率的高清實時視頻聊天","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1988-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"騰訊技術分享:微信小程序音視頻與WebRTC互通的技術思路和實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1992-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"手把手教你讀取Android版微信和手Q的聊天記錄(僅作技術研究學習)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1998-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-1999-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信技術分享:微信的海量IM聊天消息序列號生成實踐(容災方案篇)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2032-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"騰訊技術分享:GIF動圖技術詳解及手機QQ動態表情壓縮技術實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2066-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:Kotlin漸被認可,Android版微信的技術嚐鮮之旅","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2519-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"社交軟件紅包技術解密(二):解密微信搖一搖紅包從0到1的技術演進","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2533-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"社交軟件紅包技術解密(三):微信搖一搖紅包雨背後的技術細節","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2548-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"社交軟件紅包技術解密(四):微信紅包系統是如何應對高併發的","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2564-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"社交軟件紅包技術解密(五):微信紅包系統是如何實現高可用性的","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2568-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"社交軟件紅包技術解密(六):微信紅包系統的存儲層架構演進實踐","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3125-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"社交軟件紅包技術解密(十一):解密微信紅包隨機算法(含代碼實現)","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2873-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:極致優化,iOS版微信編譯速度3倍提升的實踐總結","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2887-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"IM“掃一掃”功能很好做?看看微信“掃一掃識物”的完整技術實現","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-2958-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:微信支付代碼重構帶來的移動端軟件架構上的思考","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3008-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"IM開發寶典:史上最全,微信各種功能參數和邏輯規則資料彙總","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"《","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3376-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"微信團隊分享:微信直播聊天室單房間1500萬在線的消息架構演進之路","attrs":{}}]},{"type":"text","text":"》","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">> ","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/forum.php%3Fmod%3Dcollection%26action%3Dview%26ctid%3D12","title":null,"type":null},"content":[{"type":"text","text":"更多同類文章 ……","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"(本文同步發佈於:","attrs":{}},{"type":"link","attrs":{"href":"http://www.52im.net/thread-3594-1-1.html","title":null,"type":null},"content":[{"type":"text","text":"http://www.52im.net/thread-3594-1-1.html","attrs":{}}]},{"type":"text","text":")","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章