Android開發高手課NOTE

最近學習了極客時間的《Android開發高手課》頗有收穫,記錄總結一下。
歡迎學習老師的專欄:Android開發高手課前端

內存優化
卡頓的緣由
頻繁 GC 形成卡頓、物理內存不足時系統會觸發 low memory killer 機制,系統負載太高是形成卡頓的倆個緣由。linux

除了頻繁 GC 形成卡頓以外,物理內存不足時系統會觸發 low memory killer 機制,系統負載太高是形成卡頓的另一個緣由。「用時分配,及時釋放」android

Android 3.0~Android 7.0 將 Bitmap 對象和像素數據統一放到 Java 堆中,這樣就算咱們不調用 recycle,Bitmap 內存也會隨着對象一塊兒被回收。不過 Bitmap 是內存消耗的大戶,把它的內存放到 Java 堆中彷佛不是那麼美妙。即便是最新的華爲 Mate 20,最大的 Java 堆限制也纔到 512MB,可能個人物理內存還有 5GB,可是應用仍是會由於 Java 堆內存不足致使 OOM。Bitmap 放到 Java 堆的另一個問題會引發大量的 GC,對系統內存也沒有徹底利用起來。
將 Bitmap 內存放到 Native 中,也能夠作到和對象一塊兒快速釋放,同時 GC 的時候也能考慮這些內存防止被濫用。NativeAllocationRegistry 能夠一次知足你這三個要求,Android 8.0 正是使用這個輔助回收 Native 內存的機制,來實現像素數據放到 Native 內存中。Android 8.0 還新增了硬件位圖 Hardware Bitmap,它能夠減小圖片內存並提高繪製效率。git

// 步驟一:申請一張空的 Native Bitmap
Bitmap nativeBitmap = nativeCreateBitmap(dstWidth, dstHeight, nativeConfig, 22);

// 步驟二:申請一張普通的 Java Bitmap
Bitmap srcBitmap = BitmapFactory.decodeResource(res, id);

// 步驟三:使用 Java Bitmap 將內容繪製到 Native Bitmap 中
mNativeCanvas.setBitmap(nativeBitmap);
mNativeCanvas.drawBitmap(srcBitmap, mSrcRect, mDstRect, mPaint);

// 步驟四:釋放 Java Bitmap 內存
srcBitmap.recycle();
srcBitmap = null;

sampling模式和instrumentation模式的區別
二者的區別:程序員

  • 在sampling模式中,profiler以固定的間隔對運行中的程序進行採樣,根據採樣結果統計出程序中各個部分的開銷。
  • 在instrumentation模式中,profiler對運行中的程序所執行的每個指令都進行記錄,最後根據這份記錄生成程序中各個部分的開銷。

在實際使用中:github

  • sampling模式速度快,記錄產生的數據量小,可是統計結果並不十分精確,適合於對程序全局性能進行初步的分析,找出程序瓶頸大體的「區間」。
  • instrumentation模式能精確記錄程序各個部分的開銷,可是速度慢,記錄產生的數據量大,適合於對程序局部進行精細分析,精肯定位瓶頸位置。

捕獲堆轉儲
使用:點擊 Dump Java heap 
堆轉儲顯示在您捕獲堆轉儲時您的應用中哪些對象正在使用內存。 特別是在長時間的用戶會話後,堆轉儲會顯示您認爲不該再位於內存中卻仍在內存中的對象,從而幫助識別內存泄漏。 在捕獲堆轉儲後,您能夠查看如下信息:web

  • 您的應用已分配哪些類型的對象,以及每一個類型分配多少。、
  • 每一個對象正在使用多少內存。
  • 在代碼中的何處仍在引用每一個對象。
  • 對象所分配到的調用堆棧(目前,若是您在記錄分配時捕獲堆轉儲,則只有在 Android 7.1 及更低版本中,堆轉儲才能使用調用堆棧)

在您的堆轉儲中,請注意由下列任意狀況引發的內存泄漏:面試

  • 長時間引用 Activity、Context、View、Drawable 和其餘對象,可能會保持對 Activity 或 Context 容器的引用。
  • 能夠保持 Activity 實例的非靜態內部類,如 Runnable。
  • 對象保持時間超出所需時間的緩存。

分析內存的技巧
使用 Memory Profiler 時,您應對應用代碼施加壓力並嘗試強制內存泄漏。 在應用中引起內存泄漏的一種方式是,先讓其運行一段時間,而後再檢查堆。 泄漏在堆中可能逐漸匯聚到分配頂部。 不過,泄漏越小,您越須要運行更長時間的應用才能看到泄漏。
您還能夠經過如下方式之一觸發內存泄漏:json

  • 將設備從縱向旋轉爲橫向,而後在不一樣的 Activity 狀態下反覆操做屢次。 旋轉設備常常會致使應用泄漏 Activity、Context 或 View 對象,由於系統會從新建立 Activity,而若是您的應用在其餘地方保持對這些對象之一的引用,系統將沒法對其進行垃圾回收。
  • 處於不一樣的 Activity 狀態時,在您的應用與另外一個應用之間切換(導航到主屏幕,而後返回到您的應用)。

ANR後端

  • 個人經驗是,先看看主線程的堆棧,是不是由於鎖等待致使。接着看看 ANR 日誌中 iowait、CPU、GC、system server 等信息,進一步肯定是 I/O 問題,或是 CPU 競爭問題,仍是因爲大量 GC 致使卡死.從 Logcat 中咱們能夠看到當時系統的一些行爲跟手機的狀態,例如出現 ANR 時,會有「am_anr」;App 被殺時,會有「am_kill」。
  • 查找共性,機型、系統、ROM、廠商、ABI,這些採集到的系統信息均可以做爲維度聚合,在文中我提到 Hprof 文件裁剪和重複圖片監控,這是不少應用目前都沒有作的,而這兩個功能也是微信的 APM 框架 Matrix 中內存監控的一部分。Matrix 是我一年多前在微信負責的最後一個項目,也付出了很多心血,最近據說終於準備開源了。那今天咱們就先來練練手,嘗試使用 HAHA 庫快速判斷內存中是否存在重複的圖片,而且將這些重複圖片的 PNG、堆棧等信息輸出

SharedPreferences的問題

  1. 跨進程不安全。SharedPreferences 在跨進程頻繁讀寫有可能致使數據所有丟失。根據線上統計,SP 大約會有萬分之一的損壞率。
  2. 加載緩慢。SharedPreferences 文件的加載使用了異步線程,並且加載線程並無設置線程優先級,若是這個時候主線程讀取數據就須要等待文件加載線程的結束。
    這就致使出現主線程等待低優先級線程鎖的問題,好比一個 100KB 的 SP 文件讀取等待時間大約須要 50~100ms,我建議提早用異步線程預加載啓動過程用到的 SP 文件
  3. 全量寫入。不管是調用 commit() 仍是 apply(),即便咱們只改動其中的一個條目,都會把整個內容所有寫到文件。並且即便咱們屢次寫入同一個文件,SP 也沒有將屢次修改合併爲一次,這也是性能差的重要緣由之一。
  4. 卡頓。因爲提供了異步落盤的 apply 機制,在崩潰或者其餘一些異常狀況可能會致使數據丟失。因此當應用收到系統廣播,或者被調用 onPause 等一些時機,系統會強制把全部的 SharedPreferences 對象數據落地到磁盤。若是沒有落地完成,這時候主線程會被一直阻塞。

使用mmkv

mmap將一個文件或者其它對象映射進內存。(linux上的東西)
常規文件操做須要從磁盤到頁緩存再到用戶主存的兩次數據拷貝。(從磁盤拷貝到頁緩存中,因爲頁緩存處在內核空間,不能被用戶進程直接尋址,因此還須要將頁緩存中數據頁再次拷貝到內存對應的用戶空間中)
而mmap操控文件,只須要從磁盤到用戶主存的一次數據拷貝過程。說白了,mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不一樣數據不通的繁瑣過程。所以mmap效率更高。

Serializable

  • 整個序列化過程使用了大量的反射和臨時變量,並且在序列化對象的時候,不只會序列化當前對象自己,還須要遞歸序列化對象引用的其餘對象。
  • 整個過程計算很是複雜,並且由於存在大量反射和 GC 的影響,序列化的性能會比較差。另一方面由於序列化文件須要包含的信息很是多,致使它的大小比 Class 文件自己還要大不少,這樣又會致使 I/O 讀寫上的性能問題
  • Parcel 序列化和 Java 的 Serializable 序列化差異仍是比較大的,Parcelable 只會在內存中進行序列化操做,並不會將數據存儲到磁盤裏。經過取巧的方法能夠實現 Parcelable 的永久存儲,可是它也存在兩個問題。

Serial的優勢

  1. 相比起傳統的反射序列化方案更加高效(沒有使用反射)
  2. 性能相比傳統方案提高了3倍 (序列化的速度提高了5倍,反序列化提高了2.5倍)
  3. 序列化生成的數據量(byte[])大約是以前的1/5
  4. 開發者對於序列化過程的控制較強,可定義哪些object、field須要被序列化
  5. 有很強的debug能力,能夠調試序列化的過程(詳見:調試)

數據的序列化
Serial 性能看起來還不錯,可是對象的序列化要記錄的信息仍是比較多,在操做比較頻繁的時候,對應用的影響仍是很多的,這個時候咱們能夠選擇使用數據的序列化。
json:原生、gosn、fastjson(數據量大了的時候最快)

文件遍歷在 API level 26 以後建議使用FileVisitor,替代 ListFiles,總體的性能會好不少。

Mars的好處就是跨平臺、長連接,看狀況

網絡數據壓縮

電量

  • Android 是基於 Linux 內核,而 Linux 大部分使用在服務器中,它對功耗並無作很是嚴格苛刻的優化。特別是國內會有各類各樣的「保活黑科技」,大量的應用在後臺活動簡直就是「電量黑洞」。

  • 耗電量這塊, 由於要維持推送的實時到達, 只能追求黑科技, 要否則人家就會問,爲啥蘋果能夠收到推送,android就不行~ 可是保活就會加大耗電

  • 耗電優化的第一個方向是優化應用的後臺耗電。由於用戶最容易感知這個,我明明沒有怎麼打開,爲何耗這麼多?在後臺不要作這些:長時間獲取 WakeLock(及時釋放)、WiFi 和藍牙的掃描、GPS、video、audio

WakeLock 用來阻止 CPU、屏幕甚至是鍵盤的休眠。相似 Alarm、JobService 也會申請 WakeLock 來完成後臺 CPU 操做.
Alarm 用來作一些定時的重複任務

經過 Hook,咱們能夠在申請資源的時候將堆棧信息保存起來。當咱們觸發某個規則上報問題的時候,能夠將收集到的堆棧信息、電池是否充電、CPU 信息、應用先後臺時間等輔助信息也一塊兒帶上。

UI優化

autosize是頭條方案,經過反射修改系統的density值

對於硬件繪製,咱們經過調用 OpenGL ES 接口利用 GPU 完成繪製。opengl是一個跨平臺的圖形 API,它爲 2D/3D 圖形處理硬件指定了標準軟件接口。而 OpenGL ES 是 OpenGL 的子集,專爲嵌入式設備設計。

使用 XML 進行 UI 編寫能夠說是十分方便,能夠在 Android Studio 中實時預覽到界面。若是咱們要對一個界面進行極致優化,就可使用代碼進行編寫界面。

xml缺點
讀取xml很耗時
遞歸解析xml較耗時
反射生成對象的耗時是new的3倍以上
x2c:在編譯的時候,經過註解的方式,將xml轉換成Java代碼

利用卡頓優化中的traceview或者systrace定位是最高效的

measure/layout 優化

  • 減小布局的嵌套(viewstub、merge、include)
  • 儘可能不使用 RelativeLayout 或者基於 weighted LinearLayout,它們 layout 的開銷很是巨大。這裏我推薦使用 ConstraintLayout (約束佈局,只有一個層級)替代 RelativeLayout 或者 weighted LinearLayout。
  • 減小多餘的background
  • PrecomputedText(研究下),異步的textview。
    Litho (研究下)如我前面提到的 PrecomputedText 同樣,把 measure 和 layout 都放到了後臺線程,只留下了必需要在主線程完成的 draw,這大大下降了 UI 線程的負載。
    若是你沒有計劃徹底遷移到 Litho,我建議能夠優先使用 Litho 中的 RecyclerCollectionComponent 和 Sections 來優化本身的 RecyelerView 的性能。

減小apk體積

  • Android Studio 3.0 推出了新 Dex 編譯器 D8 與新混淆工具 R8,目前 D8 已經正式 Release,大約能夠減小 3% 的 Dex 體積。可是計劃用於取代 ProGuard 的依然處於實驗室階段,期待它在將來能有更好的表現。
  • MultiDex.install(this);分多個dex包
  • 使用andresguard,路徑變成了r/d/a,還有Android 編譯過程當中,下面這些格式的文件會指定不壓縮;在 AndResGuard 中,咱們支持針對 resources.arsc、PNG、JPG 以及 GIF 等文件的強制壓縮。
  • 移出無用的資源
android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
        }
    }
}

加快編譯速度

  • 你能夠把編譯簡單理解爲,將高級語言轉化爲機器或者虛擬機所能識別的低級語言的過程。對於 Android 來講,這個過程就是把 Java 或者 Kotlin 轉變爲 Android 虛擬機運行的Dalvik 字節碼
  • 關閉 JITandroid:vmSafeMode=「true」,關閉虛擬機的 JIT 優化
  • r8/d8
  • 使用tinker,能夠回退

線上問題排查

  • 日誌打點怕打太多也怕太少,擔憂出現問題沒有足夠豐富的信息去定位分析問題。應該打多少日誌,如何去打日誌並無一個很是嚴格的準則,這須要整個團隊在長期實踐中慢慢去摸索。在最開始的時候,可能你們都不重視也不肯意去增長關鍵代碼的日誌,可是當咱們經過日誌平臺解決了一些疑難問題之後,團隊內部的成功案例愈來愈多的時候,這種習慣也就慢慢創建起來了。
  • 使用Mars的xlog,Java實現寫日誌,GC頻繁,而C實現並不會出現這種狀況,由於它不會佔用Java的堆內存。
  • 倆種方式上報日誌:push上報,主動上報(在用戶出現奔潰,反饋問題時主動上報日誌(能夠重啓了上報))
  • 正由於反覆「痛過」,纔會有了微信的用戶日誌和點擊流平臺,纔會有美團的 LoganHomles(看看) 統一日誌系統。所謂團隊的「提質增效」,就是尋找團隊中這些痛點,思考如何去改進。不管是流程的自動化,仍是開發新的工具、新的平臺,都是朝着這個目標前進。

h5優化
分爲前端優化和本地優化
前端優化

本地:

  • webview預建立。提早建立和初始化 WebView,以及實現 WebView 的複用,這塊大約能夠節省 100~200 毫秒。
  • 緩存。提早把網頁須要的資源請求下來。

React Native 和 Weex 性能差。 JS 是解釋性的動態語言,它的執行效率相比 AOT 編譯後的 Java,性能依然會在幾倍以上的差距。

音視頻
對於咱們來講,最多見的視頻格式就是MP4格式,這是一個通用的容器格式。所謂容器格式,就意味內部要有對應的數據流用來承載內容。並且既然是一個視頻,那必然有音軌和視軌,而音軌、視軌自己也有對應的格式。常見的音軌、視軌格式包括:

視軌:其中,目前大部分 Android 手機都支持 H.264 格式的直接硬件編碼和解碼;對於 H.265 來講,Android 5.0 以上的機器就支持直接硬件解碼了,可是對於硬件編碼,目前只有一部分高端芯片能夠支持,例如高通的 8xx 系列、華爲的 98x 系列。對於視軌編碼來講,分辨率越大性能消耗也就越大,編碼所需的時間就越長。
音軌:AAC

同一個壓縮格式下,碼率越高質量也就越好。
咱們分別從攝像頭 / 錄音設備採集數據,將數據送入編碼器,分別編碼出視軌 / 音軌以後,再送入合成器(MediaRemuxer 或者相似 mp4v二、FFmpeg 之類的處理庫),最終輸出 MP4 文件。
對於目前的視頻類 App 來講,還有各類各樣的濾鏡和美顏效果,實際上均可以基於 OpenGL 來實現。

播放的視頻多是做爲視頻編輯的一部分,在剪輯時須要實時預覽視頻特效。咱們能夠簡單配置播放視頻的 View 爲一個 GLSurfaceView,有了 OpenGL 的環境,咱們就能夠在這上實現各類特效、濾鏡的效果了。而對於視頻編輯常見的快進、倒放之類的播放配置,MediaPlayer 也有直接的接口能夠設置。
MediaPlayer沒法精準的seek


關於學習

  • 年輕人千萬不要碰的東西之一,即是能得到短時間快感的軟件。它們會在不知不覺中偷走你的時間,消磨你的意志力,摧毀你向上的勇氣。
  • 須要看產出,而是否是工做時長
  • 天天咱們應該須要有一段時間真正的靜下心來工做,並且每過一段時間也要從新審視一下本身的工做,有哪些地方作的不夠好?有沒有什麼事情是本身或者團隊的人正在反覆而低效在作的,是否能夠優化。
  • 人的精力是有限的,天天可能也就有2個多小時能高效的產出,必定要把握好這個時間,留給最重要的事情。天天早上通勤的路上或者到公司的前10分鐘能夠好好規劃一下當天要作的事情。
  • 所謂的「T」無非就是橫向和縱向兩個維度。縱向解決的是深度問題,橫向解決的是廣度問題。
  • 若是你在大廠,就應該從客戶端到後端,儘量全面深刻研究你參與的模塊,多想一想如何把你所作的模塊優化到極致,而且在巨大的用戶量面前依然可以穩定運行。若是你在初創團隊,在業餘時間也要堅持學習,持續探索本身的技術深度。這樣在未來,不管是初創團隊內部的晉升,仍是跳到大廠,這樣努力的經驗均可以成爲將來無數次面試、加薪的一大亮點。
  • 我建議你應該至少先在一個技術領域付出大量的精力,深刻鑽研透徹,而後再去思考廣度的問題。這是由於經驗豐富的程序員學新的東西都很是快,由於如今已經不那麼容易出現太多全新的技術,所謂的新技術其實都是舊技術的從新組合和微創新。
  • 如今好像有個觀點說「Android 開發沒人要」,你們都想轉去作大前端開發,是否是真的是這樣呢?事實上,不管咱們使用哪種跨平臺方案,它們最終都要運行在 Android 平臺上。崩潰、內存、卡頓、耗電這些問題依然存在,並且可能會更加複雜。並且從 H5 極致體驗優化的例子來看,不少優化是須要深刻研究平臺特性和系統底層機制,咱們在「高質量開發」中學到的底層和系統相關的知識依然很重要。
  • 這一系列涉及的內容就很是複雜,但每一項單拆出來去看,一層層的去學習和補充,就會感受容易不少。這一點其實在業務開發上也有體現,咱們剛接手一個複雜的業務,代碼龐大,註釋和文檔都不多,但在一段時間後你仍是會對整個業務有或多或少的認識,在接到新的業務的時候也沒有以爲難到無從下手,頂可能是以爲複雜。底層的系統、框架也是如此,這是一個由點到面的過程。
  • 在平時零散的時間裏咱們看到一篇技術文章,並非閱讀收藏後就結束了,這樣你可能會在很短的時間裏就忘掉了文章的內容。他將閱讀一篇文章分紅如下幾個步驟:提取這篇文章要解決的問題;而後歸納一下涉及的技術點;提取重點內容,好比問題發生的原因、有哪幾種解決方法。整體來講,這個方法是爲了在短期內提取出重點內容,而後記錄下來後面再進行復習。因此咱們都須要多記錄、多複習,能夠培養使用一些工具來幫助本身養成習慣
  • 惟有學習,不可辜負
  • 確認嚴重程度。解決崩潰也要看性價比,咱們優先解決 Top 崩潰或者對業務有重大影響,例如啓動、支付過程的崩潰。我曾經有一次辛苦了幾天解決了一個大的崩潰,但下個版本產品就把整個功能都刪除了,這令我很崩潰
相關文章
相關標籤/搜索