Android元件化實踐專案分享

楊充發表於2019-03-06

目錄介紹

  • 01.專案介紹
  • 02.專案執行
  • 03.專案部分介紹
  • 04.專案元件化結構
  • 05.專案版本更新
  • 06.專案第三方庫
  • 07.專案遇到bug
  • 08.專案截圖展示
  • 09.專案優化處理
  • 10.元件化部落格
  • 11.其他介紹

01.專案介紹

1.1 專案簡介

  • 專案整體架構模式採用:元件化+MVP+Rx+Retrofit+design+Dagger2+阿里VLayout+騰訊X5+騰訊bugly
  • 包含的模組:wanAndroid【kotlin】+乾貨集中營+知乎日報+番茄Todo+微信精選新聞+豆瓣音樂電影小說+小說讀書+簡易記事本+搞笑視訊+經典遊戲+其他更多等等
  • 此專案屬於業餘時間練手的專案,介面資料來源均來自網路,如果存在侵權情況,請第一時間告知。本專案僅做學習交流使用,API資料內容所有權歸原作公司所有,請勿用於其他用途。
  • 可以先下載apk執行到手機上看看效果,下載連結地址:

02.專案執行

  • 執行環境要求
    • Android studio 版本需要在3.0之上,compileSdkVersion是28,gradle版本是3.2.1,gradle-wrapper是4.6
  • 元件模式和整合模式如何切換
    • 預設模式下,都是屬於library形式的元件【app作為空殼主工程依賴所有的元件】,如果想把某一個library形式的元件切換成一個獨立可以run的application,則
    • 比如,我想把視訊模組,也就是該專案中的video元件切換成可執行的專案。如果想了解元件化更多內容,可以著重看 04.專案元件化結構部分說明
    • 修改yc.gradle檔案中,直接將對應的開關置為true即可,然後需要Sync一下,就可以執行該模組
      • 將isVideoApplication = true,就可以切換成application模組,與app主工程解綁,實際開發中,比如你負責這個模組,那麼你執行的時候,直接編譯該模組,不會編譯整個專案而導致耗費大量時間。
      • 同理,設定成false,即可還原成library形式的元件,與app主工程繫結[也就是被主工程依賴]
      ext {
          isAndroidApplication = false  //玩Android模組開關,false:作為Lib元件存在, true:作為application存在
          isVideoApplication = true  //視訊模組開關,false:作為Lib元件存在, true:作為application存在
          isNoteApplication = false  //記事本模組開關,false:作為Lib元件存在, true:作為application存在
          isGameApplication = false  //遊戲模組開關,false:作為Lib元件存在, true:作為application存在
      複製程式碼
  • 模組眾多,如何統一修改第三方庫,以及build.gradle中的配置資訊
    • 已經抽取了公共的build.gradle,詳細的程式碼,可以直接看專案根目錄下的yc.gradle檔案,修改版本即可
  • 切換網路請求方式,比如公司正式專案就有:正式線上環境,測試環境,模擬環境等等
    • 那麼如何統一切換呢,直接修改專案根目錄下的url.properties檔案即可。由於該專案眾多介面是網路介面,因此該專案只是提供切換環境的思路程式碼。
      //修改TEST_URL即可,0測試環境,1模擬環境,2正式環境
      TEST_URL=0
      PREVIEW_URL=1
      RELEASE_URL=2
      複製程式碼
  • 編譯時間
    • 由於元件化模組眾多,第一次編譯時間會較長,請耐心等待。專案編譯成功之後,後期可以較快執行專案。自己每次執行專案,大概是2到5分鐘
    • 如果出現編譯失敗,可以看看該目錄下7.1編譯失敗,記錄常見編譯錯誤。如果其他問題,大都可以谷歌解決。如果編譯耗時,可以根據run build檢視每個模組的編譯時間【也就是找出編譯耗時所在,作為程式設計師不僅要知道編譯為何時間長,還要找出那個地方編譯時間長】,所有元件(初基礎公共元件外)均可和主工程app解綁。
    • image
  • 編譯遇到問題
    • 看看7.1 編譯報錯bug,如果沒有找到解決方案,建議谷歌查詢一下問題。

03.專案部分介紹

3.1 專案包含的模組

  • 新聞部分(天行新聞,微信精選新聞,阿里雲熱門新聞,乾貨集中營新聞等等)
    • 乾貨集中營:包含福利,搜尋,每日技術新聞,休息視訊等眾多模組
  • 音樂部分(音樂播放器,自動搜尋本地音樂檔案,線上音樂是百度音樂api)
    • 播放本地音樂,網路音樂[支援下載,播放,分享],搜尋音樂
  • 視訊部分(視訊播放器,自動搜尋本地視訊檔案,還有許多網路搞笑視訊)
  • 圖片部分(乾貨集中營美女圖片),畫廊瀏覽高清大圖
  • 豆瓣電影,音樂,讀書(豆瓣介面)
  • 簡易ToDo記事本,番茄周,學習MVP+Dagger2時寫的。
  • 超文字筆記本,可以支援文字,圖片,動態圖混排,做便籤十分方便,注意高清圖片會壓縮,目前筆記是儲存本地
  • 技術分享部分(鴻洋玩Android,還有程式碼家的幹活集中營等等),關於flutter版本的極致體驗玩Android客戶端
    • 玩Android,鴻洋大神的開放介面,
    • 首頁輪播圖+list:推薦最新的部落格
    • 知識體系:對安卓知識體系做整理
    • 登入註冊:登入、註冊、Cookie持久化
    • 我的收藏頁面:依靠Cookie持久化,實現對文章的收藏和展示
    • 專案分類:在WanAndroid上釋出的專案
    • 網址導航:展示常用的開發網站
    • 搜尋功能:輸入搜尋、搜尋推薦、歷史搜尋等等
    • 關於我們:鴻洋wanAndroid介紹
  • love愛意表達部分,程式設計師表白神器,簡易含蓄的單身程式設計師可以看看該模組,可以給女朋友一個驚喜!
  • 玩Android部分,介面是鴻洋大神開放的api,學習kotlin時所寫
  • markDown格式筆記本,支援md格式,資料是儲存到本地。對於程式設計師,markDown是十分方便記錄筆記
  • 遊戲部分,包括智慧拼圖,還有童年飛機大戰遊戲,體驗非常好玩。
  • 其他部分,幾乎融合了自己開源的大部分封裝庫,比如,狀態管理,視訊庫,輪播圖,幸運大轉盤[老虎機],畫廊,自定義進度條,圖片縮放,執行緒池

3.2 相關特性說明

  • 側滑選單:DrawerLayout+NavigationView
  • 基本遵循Google Material Design設計風格
  • 透明狀態列使用與版本適配
  • 圖片載入picasso,Glide載入監聽,獲取快取,圓角圖片,高斯模糊
  • list條目點選水波紋效果
  • CoordinatorLayout+Behavior實現標題欄漸變
  • 自定義RecyclerView下拉重新整理上拉載入,支援載入loading,空頁面,異常介面,有資料介面狀態切換
  • 快取使用Realm資料庫,做資料的增刪改查
  • 狀態管理庫與Activity和Fragment結合,可以自由切換不同的狀態

3.3 專案優化點內容

  • 專案程式碼規範;佈局優化;程式碼優化;架構優化;記憶體洩漏優化;執行緒優化;Bitmap優化;網路優化;懶載入優化,啟動頁優化;靜態變數優化;電量效能優化;view控制元件異常銷燬儲存重要資訊優化;去除淡黃色警告優化;使用註解替代列舉優化;glide加速優化;多渠道打包優化狀態管理切換優化;TrimMemory和LowMemory優化;輪詢操作優化;去除重複依賴庫優化
  • 具體可以看目錄09.專案優化處理部分內容!!!

04.專案元件化結構

4.1 傳統APP架構圖

  • 傳統APP架構圖
    • 如圖所示,從網上摘來的……
    • image
  • 存在的問題
    • 普遍使用的 Android APP 技術架構,往往是在一個介面中存在大量的業務邏輯,而業務邏輯中充斥著各種網路請求、資料操作等行為,整個專案中也沒有模組的概念,只有簡單的以業務邏輯劃分的資料夾,並且業務之間也是直接相互呼叫、高度耦合在一起的。單一工程模型下的業務關係,總的來說就是:你中有我,我中有你,相互依賴,無法分離。如下圖:
    • image

4.2 專案元件化結構

  • 主工程:
    • 除了一些全域性配置和主 Activity 之外,不包含任何業務程式碼。有的也叫做空殼app
  • 業務元件:
    • 最上層的業務,每個元件表示一條完整的業務線,彼此之間互相獨立。
    • 該案例中分為:幹活集中營,玩Android,知乎日報,微信新聞,頭條新聞,搞笑視訊,百度音樂,我的記事本,豆瓣音樂讀書電影,遊戲元件等等。
  • 功能元件:
    • 該案例中分為,分享元件,評論反饋元件,支付元件,畫廊元件等等。同時注意,可能會涉及多個業務元件對某個功能元件進行依賴!
  • 基礎元件:
    • 支撐上層業務元件執行的基礎業務服務。
    • 該案例中:在基礎元件庫中主要有,網路請求,圖片載入,通訊機制,工具類,分享功能,支付功能等等。當然,我把一些公共第三方庫放到了這個基礎元件中!

4.3 專案元件化架構圖,如下所示

  • image

4.4 元件通訊是通過路由轉發

  • 傳統以前工程下模組
    • 記得剛開始進入Android開發工作時,只有一個app主工程,後期幾乎所有的需求都寫在這個app主工程裡面。只有簡單的以業務邏輯劃分的資料夾,並且業務之間也是直接相互呼叫、高度耦合在一起的。
    • 導致後期改專案為元件化的時候十分痛苦,不同模組之間的業務邏輯實在關聯太多,但還是沒辦法,於是按照步驟一步步實踐。終極目標是,告別結構臃腫,讓各個業務變得相對獨立,業務元件在元件模式下可以獨立開發。
  • 元件化模式下如何通訊
    • 這是元件化工程模型下的業務關係,業務之間將不再直接引用和依賴,而是通過“路由”這樣一箇中轉站間接產生聯絡。在這個開源專案中,我使用的阿里開源的路由框架。關於Arouter基礎使用和程式碼分析,可以看我這篇部落格:Arouter使用與程式碼解析
    • image

4.5 關於元件遇到的問題

  • 那麼問題有哪些呢?
    • 元件化時資源名衝突該怎麼辦?比如,color,shape,drawable,圖片資源,佈局資源,或者anim資源等等,都有可能造成資源名稱衝突。這是為何了,有時候大家負責不同的模組,如果不是按照統一規範命名,則會偶發出現該問題。
    • 業務元件之間聯動導致耦合嚴重?比如,實際開發中,購物車和首頁商品分別是兩個元件。但是遇到產品需求,比如過節做個活動,發個購物券之類的需求,由於購物車和商品詳情頁都有活動,因此會造成元件經常會發生聯動。
    • 如何做到各個元件化模組能獲取到全域性上下文?
    • 元件在lib和app之間進行切換,如何處理butterKnife使用問題呢?
    • 當元件是lib時,寫程式碼需要注意哪些語法?
    • 不要亂髮bus訊息?如果專案中大量的使用eventbus,那麼會看到一個類中有大量的onEventMainThread()方法,寫起來很爽,閱讀起來很痛苦。
    • 頁面跳轉存在問題?比如,跳轉頁面需要登陸狀態如何攔截,跳轉頁面傳遞引數該怎麼辦,程式意外跳轉異常或者失敗又該如何處理?
    • 使用Arouter注意事項有哪些?如何讓程式碼變得更加容易讓人維護?
    • 直接看我這篇部落格:https://juejin.im/post/5c46e6fb6fb9a049a5713bcc

4.6 存在待解決問題

  • 動態的管理元件,所以給每個元件新增幾個生命週期狀態:載入、解除安裝和降維。為此我們給每個元件增加一個ApplicationLike類,裡面定義了onCreate和onStop兩個生命週期函式。
    • 看到網上有個方案說:主專案負責載入元件,由於主專案和元件之間是隔離的,那麼主專案如何呼叫元件ApplicationLike的生命週期方法呢,目前採用的是基於編譯期位元組碼插入的方式,掃描所有的ApplicationLike類(其有一個共同的父類),然後通過javassist在主專案的onCreate中插入呼叫ApplicationLike.onCreate的程式碼。那麼思路有了,具體程式碼該如何實現?

4.7 整合模式和元件模式

  • 可以在yc.gradle檔案自由設定切換模式
    • image

4.7 元件化中Fragment通訊難點

  • 在網上看到很多部落格說,如何拆分元件,按模組拆分,或者按照功能拆分。但很少有提到fragment在拆分元件時的疑問,這個讓我很奇怪。
  • 先來說一個業務需求,比如一個購物商城app,有4個模組,做法一般是一個activity+4個fragment,這個大家都很熟悉,這四個模組分別是:首頁,發現,購物車,我的。然後這幾個頁面是用fragment寫的,共用一個宿主activity,那麼在做元件化的時候,我想把它按照業務拆分成首頁,發現,購物車和我的四個獨立的業務模組。
  • 遇到疑問:
    • 如果是拆分成四個獨立的業務模組,那麼對應的fragment肯定要放到對應的元件中,那麼這樣操作,當主工程與該業務元件解綁的情況下,如何拿到fragment和傳遞引數進行通訊。
    • Fragment 中 開啟Activity帶requestCode,開啟的Activity關閉後,不會回撥Fragment中的onActivityResult。只會呼叫Fragment 所在Activity的onActivityResult。
    • 多fragment單activity攔截器不管用,難道只能用於攔截activity的跳轉?那如果是要實現登入攔截的話,那不是隻能在PathReplaceService中進行了?
  • 網路解決辦法
    • 第一個疑問:由於我使用阿里路由,所以我看到zhi1ong大佬說:用Router跳轉到這個Activity,然後帶一個引數進去,比方說tab=2,然後自己在onCreate裡面自行切換。但後來嘗試,還是想問問廣大程式設計師有沒有更好的辦法。
    • 第二個疑問:還是zhi1ong大佬說,通過廣播,或者在Activity中轉發這個事件,比方說讓Fragment統一依賴一個介面,然後在Activity中轉發。

4.8 元件化實踐中的切換run

  • 你完全可以採取拿來注意,將任意一個元件拿來即用即可。
    • image

05.專案版本更新

06.專案第三方庫

07.專案遇到bug

7.1 編譯報錯bug

  • Caused by: org.gradle.tooling.BuildException: Failed to execute aapt
    • 有時候,報這個錯誤沒有明確指出具體的問題程式碼。網上有的說是檢測.9圖片出錯,或者資原始檔出錯。但究竟是哪裡出錯,能否通過日誌展示具體的位置,這樣更方便排查問題。
    • 第一種解決方案
      • 網上有解決方案說,開啟gradle.properties,新增如下內容:android.enableAapt2=false
      • 在2018年之後使用“android.enableAapt2=false”來關閉AAPT2是行不通的,這個方法已經過時
    • 第二種解決方案
      • 在命令列輸入,gradlew compileDebugSources,可以檢視列印報錯的資訊,這句話可以控制檯輸出程式碼報錯的日誌。
  • IOException: CreateProcess error=2, 系統找不到指定的檔案。
    • 具體報錯日誌如下所示
      IOException: Cannot run program "D:\Program File\AndroidSdk\ndk-bundle\toolchains\mips64el-linux-android-4.9\prebuilt\windows-x86_64\bin\mips64el-linux-android-strip" (in directory "D:\GitHub\LifeHelper\app"): CreateProcess error=2, 系統找不到指定的檔案。
      複製程式碼
    • 原因分析:Android/Sdk/ndk-bundle/toolchains/mips64el-linux-android-4.9/prebuilt/linux-x86_64/bin/mips64el-linux-android-strip 找不到, 導致編譯報錯。ndk升級導致的,自己現在是17版本。
    • 第一種解決辦法:找到工程目錄下local.properties檔案,在ndk-bundle後面新增.cmd即可執行
      ndk.dir=D\:\\Program File\\AndroidSdk\\ndk-bundle.cmd
      sdk.dir=D\:\\Program File\\AndroidSdk
      複製程式碼
    • 第二種解決辦法:去官網下載一個16或者更低的版本。下載完成後,把16b版本toolchains\mips64el-linux-android-4.9\prebuilt\windows-x86_64的所有檔案copy到r17中toolchains\mips64el-linux-android-4.9\prebuilt\windows-x86_64目錄下也可以解決
  • Invoke-customs are only supported starting with Android O (–min-api 26)
    • 錯誤: -source 1.7 中不支援 lambda 表示式,請使用 -source 8 或更高版本以啟用 lambda 表示式
      android {
          //jdk1.8
          compileOptions {
              sourceCompatibility JavaVersion.VERSION_1_8
              targetCompatibility JavaVersion.VERSION_1_8
          }
      }
      複製程式碼
  • Process 'command 'build-tools\28.0.3\aapt.exe'' finished with non-zero exit value 1
    • 在StackOverflow上發現有很多人遇到了這個錯誤,不過解決方法卻各不相同。有的建議Clean然後Rebuild,有的建議修改使用記憶體,有的說是程式碼問題,也有的說是資源問題,比如本來是jpg圖片或者.9圖片,檔案字尾卻是png也會導致問題…
    • 需要定位錯誤點,在專案根路徑,實際是控制檯Terminal輸入命令:gradlew processDebugResources --debug
    • 從Log中找到了輸出的出錯資訊,這個方法很有效,經常遇到編譯類錯誤,可以用它排除錯誤!
  • Error:Execution ':app:transformClassesWithDexForDebug'.
    • 通過gradle命令檢視重複依賴,稍等片刻,會出來一個樹狀圖,其中用"->"會標示出衝突的部分,然後解決重複依賴即可
  • DexArchiveMergerException異常:dexarchivemergeexception:合併dex存檔時出錯
    • 網上解決的方法有很多不同看法,比如:可能是64k引起的問題;可能是打包dex引起的問題;可能是jdk1.8新特性引起的問題;可能是jar包重複引用引起的問題
    重點異常資訊:
    Caused by: com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
    Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete
    Caused by: com.android.tools.r8.utils.AbortException
    複製程式碼
    • 但是思考一下,為什麼會出現這個問題,什麼情況下會出現這個問題?Android Studio 3.0 及以上版本支援所有 Java 7 語言功能,以及部分 Java 8 語言功能(具體因平臺版本而異)。
    • 引入D8作為原先Dex的升級版,升級Dex編譯器將直接影響構建時間,.dex檔案大小,執行時效能。Android Studio 3.0需要主動在gradle.properties檔案中新增:android.enableD8=true
    • Android Studio 3.1或之後D8將會被作為預設的Dex編譯器。如果遇到問題,你可以通過修改gradle.properties檔案裡的一個屬性恢復到DX android.enableD8=false,除了其他好處外,使用D8還有一個好處,就是支援 脫糖,讓Java 8才提供的特性(如lambdas)可以轉換成Java 7特性。把脫糖步驟整合進D8影響了所有讀或寫.class位元組碼的開發工具,因為它會使用Java 8格式。你可以在gradle檔案中設定一個屬性,恢復到以前的行為,讓脫糖發生在Java編譯之後,.class位元組碼仍遵循Java 7格式:android.enableD8.desugaring = false
    • 所以解決方案如下所示
    #The option 'android.enableD8' is deprecated and should not be used anymore.
    #Use 'android.enableD8=true' to remove this warning.
    #It will be removed at the end of 2018..
    android.enableD8=false
    android.enableD8.desugaring = false
    複製程式碼
  • 出現其他編譯類錯誤,可以直接谷歌搜尋解決

7.2 專案執行時bug

  • 分別列舉了實際開發中大部分的異常,主要包括1.異常Exception和2.異常Error
    • 針對開發中異常問題,大概記錄的是:該板塊是持續更新記錄專案bug問題!!
    - A.詳細崩潰日誌資訊
    - B.檢視崩潰類資訊
    - C.專案中異常分析
    - D.引發崩潰日誌的流程分析
    - F.解決辦法
    - G.其他延申
    複製程式碼
  • 01.崩潰bug日誌總結1
    • 1.1 java.lang.UnsatisfiedLinkError找不到so庫異常
    • 1.2 java.lang.IllegalStateException非法狀態異常
    • 1.3 android.content.res.Resources$NotFoundException
    • 1.4 java.lang.IllegalArgumentException引數不匹配異常
    • 1.5 IllegalStateException:Can't compress a recycled bitmap
    • 1.6 java.lang.NullPointerException空指標異常
    • 1.7 android.view.WindowManager$BadTokenException異常
    • 1.8 java.lang.ClassCastException類轉化異常
    • 1.9 Toast執行在子執行緒問題,handler問題
  • 02.崩潰bug日誌總結2
    • 1.1 java.lang.ClassNotFoundException類找不到異常
    • 1.2 java.util.concurrent.TimeoutException連線超時崩潰
    • 1.3 java.lang.NumberFormatException格式轉化錯誤
    • 1.4 java.lang.IllegalStateException: Fragment not attached to Activity
    • 1.5 ArrayIndexOutOfBoundsException 角標越界異常
    • 1.6 IllegalAccessException 方法中構造方法許可權異常
    • 1.7 android.view.WindowManager$BadTokenException,dialog彈窗異常
    • 1.8 java.lang.NoClassDefFoundError 找不到類異常
    • 1.9 Android出現:Your project path contains non-ASCII characters.
  • 03.崩潰bug日誌總結3
    • 1.1 OnErrorNotImplementedException【 Can't create handler inside thread that has not called Looper.prepare()】
    • 1.2 adb.exe,start-server' failed -- run manually if necessary
    • 1.3 java.lang.IllegalStateException: ExpectedBEGIN_OBJECT but was STRING at line 1 column 1 path $
    • 1.4 android.content.ActivityNotFoundException: No Activity found to handle Intent
    • 1.5 Package manager has died導致崩潰
    • 1.6 IllegalArgumentException View新增視窗錯誤
    • 1.7 IllegalStateException: Not allowed to start service Intent異常崩潰
    • 1.8 java.lang.IllegalStateException:Can not perform this action after onSaveInstanceState
    • 1.9 在Fragment中通過getActivity找不到上下文,報null導致空指標異常
  • [04.崩潰bug日誌總結4]
    • 1.1 IllegalArgumentException導致崩潰【url地址傳入非法引數,轉義字元】
    • 1.2 ClassNotFoundException: Didn't find class "*****" on path: /data/app/**錯誤
    • 1.3 NoClassDefFoundError異常【該異常表示找不到類定義】
    • 1.5 java.util.concurrent.ExecutionException: com.android.tools.aapt2.Aapt2Exception

08.專案截圖展示

8.1 主頁截圖

image
image
image
image
image

09.專案優化處理

9.0.1 專案程式碼規範

  • 關於程式碼規範
    • 之前阿里分享過關於編碼規範的文件,我自己也在此基礎上總結了一些程式碼規範,可以看我這篇文章:技術部落格大總結

9.0.2 佈局優化

  • 使用include標籤
    • 比如標題欄actionBar,可以抽取出來。該佈局幾乎大多數activity都會用到!
  • 可以使用ViewStub
    • 這個標籤最大的優點是當你需要時才會載入,使用他並不會影響UI初始化時的效能。各種不常用的佈局想進度條、顯示錯誤訊息等可以使用這個標籤,以減少記憶體使用量,加快渲染速度。
  • 檢視層級<merge/>
    • 這個標籤在UI的結構優化中起著非常重要的作用,它可以刪減多餘的層級,優化UI。但是就有一點不好,無法預覽佈局效果!
  • 自定義全域性的狀態管理器

9.0.3 程式碼優化

  • lint去除無效資源和程式碼
    • 如何檢測哪些圖片未被使用
      • 點選選單欄 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘app’ -> OK,這樣會搜出來哪些未被使用到未使用到xml和圖片,如下:
    • 如何檢測哪些無效程式碼
      • 使用Android Studio的Lint,步驟:點選選單欄 Analyze -> Run Inspection by Name -> unused declaration -> Moudule ‘app’ -> OK

9.0.4 架構優化

9.0.5 記憶體洩漏優化

  • 這個是一個長期在優化的東西,即使你熟悉每一種記憶體洩漏的場景,也很難徹底解決它。
    • 0.1 錯誤使用單例造成的記憶體洩漏
    • 0.2 錯誤使用靜態變數,導致引用後無法銷燬
    • 0.3 [常見]Handler使用不當造成的記憶體洩漏
    • 0.4 執行緒造成的記憶體洩漏[比較少見]
    • 0.5 非靜態內部類建立靜態例項造成的記憶體洩漏
    • 0.6 不需要用的監聽未移除會發生記憶體洩露
    • 0.7 [常見]資源未關閉造成的記憶體洩漏
    • 0.8 未登出EventBus導致的記憶體洩漏
    • 0.9 [常見]持有activity引用未被釋放導致記憶體洩漏
    • 1.0 靜態集合使用不當導致的記憶體洩漏
    • 1.1 動畫資源未釋放導致記憶體洩漏
    • 1.2 系統bug之InputMethodManager導致記憶體洩漏
    • 更加詳細的問題分析,及出現場景分析,及解決辦法等:技術部落格大總結

9.0.6 執行緒優化

  • 將全域性執行緒用執行緒池管理
    • 直接建立Thread實現runnable方法的弊端
      • 大量的執行緒的建立和銷燬很容易導致GC頻繁的執行,從而發生記憶體抖動現象,而發生了記憶體抖動,對於移動端來說,最大的影響就是造成介面卡頓
      • 執行緒的建立和銷燬都需要時間,當有大量的執行緒建立和銷燬時,那麼這些時間的消耗則比較明顯,將導致效能上的缺失
    • 為什麼要用執行緒池
      • 重用執行緒池中的執行緒,避免頻繁地建立和銷燬執行緒帶來的效能消耗;有效控制執行緒的最大併發數量,防止執行緒過大導致搶佔資源造成系統阻塞;可以對執行緒進行一定地管理。
    • 使用執行緒池管理的經典例子
      • RxJava,RxAndroid,底層對執行緒池的封裝管理特別值得參考
    • 關於執行緒池,執行緒,多執行緒的具體內容
      • 參考:輕量級執行緒池封裝庫,支援非同步回撥,可以檢測執行緒執行的狀態
      • 該專案中哪裡用到頻繁new Thread
        • 儲存圖片[注意,尤其是大圖和多圖場景下注意耗時太久];某些頁面從資料庫查詢資料;設定中心清除圖片,視訊,下載檔案,日誌,系統快取等快取內容
        • 使用執行緒池管理庫好處,比如儲存圖片,耗時操作放到子執行緒中,處理過程中,可以檢測到執行開始,異常,成功,失敗等多種狀態。

9.0.7 Bitmap優化

  • 載入圖片所佔的記憶體大小計算方式
    • 載入網路圖片:bitmap記憶體大小 = 圖片長度 x 圖片寬度 x 單位畫素佔用的位元組數【看到網上很多都是這樣寫的,但是不全面】
    • 載入本地圖片:bitmap記憶體大小 = width * height * nTargetDensity/inDensity 一個畫素所佔的記憶體。注意不要忽略了一個影響項:Density
  • 第一種載入圖片優化處理:壓縮圖片
    • 質量壓縮方法:在保持畫素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,這樣適合去傳遞二進位制的圖片資料,比如分享圖片,要傳入二進位制資料過去,限制500kb之內。
    • 取樣率壓縮方法:設定inSampleSize的值(int型別)後,假如設為n,則寬和高都為原來的1/n,寬高都減少,記憶體降低。
    • 縮放法壓縮:Android中使用Matrix對影象進行縮放、旋轉、平移、斜切等變換的。功能十分強大!
  • 第二種載入圖片優化:不壓縮載入高清圖片如何做?
    • 使用BitmapRegionDecoder,主要用於顯示圖片的某一塊矩形區域,如果你需要顯示某個圖片的指定區域,那麼這個類非常合適。
  • 關於bitmap更多優化可以看我的這篇部落格:技術部落格大總結

9.0.8 網路優化

  • 圖片網路優化
    • 比如我之前看到豆瓣介面,提供一種載入圖片方式特別好。介面返回圖片的資料有三種,一種是高清大圖,一種是正常圖片,一種是縮略小圖。當使用者處於wifi下給控制元件設定高清大圖,當4g或者3g模式下載入正常圖片,當弱網條件下載入縮圖【也稱與載入圖】。
  • 網路快取處理

9.0.9 懶載入優化

  • 該優化在新聞類app中十分常見
    • ViewPager+Fragment的搭配在日常開發中也比較常見,可用於切換展示不同類別的頁面。
    • 懶載入,其實也就是延遲載入,就是等到該頁面的UI展示給使用者時,再載入該頁面的資料(從網路、資料庫等),而不是依靠ViewPager預載入機制提前載入兩三個,甚至更多頁面的資料。這樣可以提高所屬Activity的初始化速度,也可以為使用者節省流量.而這種懶載入的方式也已經/正在被諸多APP所採用。

9.1.0 啟動頁優化

  • 啟動頁白屏優化
    • 為什麼存在這個問題?
      • 當系統啟動一個APP時,zygote程式會首先建立一個新的程式去執行這個APP,但是程式的建立是需要時間的,在建立完成之前,介面是呈現假死狀態,於是系統根據你的manifest檔案設定的主題顏色的不同來展示一個白屏或者黑屏。而這個黑(白)屏正式的稱呼應該是Preview Window,即預覽視窗。
      • 實際上就是是activity預設的主題中的android:windowBackground為白色或者黑色導致的。
      • 總結來說啟動順序就是:app啟動——Preview Window(也稱為預覽視窗)——啟動頁
    • 解決辦法
      • 常見有三種,這裡解決辦法是給當前啟動頁新增一個有背景的style樣式,然後SplashActivity引用當前theme主題,注意在該頁面將window的背景圖設定為空!
      • 更多關於啟動頁為什麼白屏閃屏,以及不同解決辦法,可以看我這篇部落格:App啟動頁面優化
  • 啟動時間優化
    • IntentService子執行緒分擔部分初始化工作
      • 現在application初始化內容有:阿里雲推送初始化,騰訊bugly初始化,im初始化,神策初始化,記憶體洩漏工具初始化,頭條適配方案初始化,阿里雲熱修復……等等。將部分邏輯放到IntentService中處理,可以縮短很多時間。
      • 開啟IntentSerVice執行緒,將部分邏輯和耗時的初始化操作放到這裡處理,可以減少application初始化時間
      • 關於IntentService使用和原始碼分析,效能分析等可以參考部落格:IntentService原始碼分析

9.1.1 靜態變數優化

  • 儘量不使用靜態變數儲存核心資料。這是為什麼呢? - 這是因為android的程式並不是安全的,包括application物件以及靜態變數在內的程式級別變數並不會一直呆著記憶體裡面,因為它很有會被kill掉。 - 當被kill掉之後,實際上app不會重新開始啟動。Android系統會建立一個新的Application物件,然後啟動上次使用者離開時的activity以造成這個app從來沒有被kill掉的假象。而這時候靜態變數等資料由於程式已經被殺死而被初始化,所以就有了不推薦在靜態變數(包括Application中儲存全域性資料靜態資料)的觀點。

9.1.2 電量效能優化

9.1.3 view控制元件異常銷燬儲存重要資訊優化

  • view自定義控制元件異常銷燬儲存狀態
    • 經常容易被人忽略,但是為了追求高質量程式碼,這個也有必要加上。舉個例子!
      @Override
      protected Parcelable onSaveInstanceState() {
          //異常情況儲存重要資訊。
          //return super.onSaveInstanceState();
          final Bundle bundle = new Bundle();
          bundle.putInt("selectedPosition",selectedPosition);
          bundle.putInt("flingSpeed",mFlingSpeed);
          bundle.putInt("orientation",orientation);
          return bundle;
      }
      
      @Override
      protected void onRestoreInstanceState(Parcelable state) {
          if (state instanceof Bundle) {
              final Bundle bundle = (Bundle) state;
              selectedPosition = bundle.getInt("selectedPosition",selectedPosition);
              mFlingSpeed = bundle.getInt("flingSpeed",mFlingSpeed);
              orientation = bundle.getInt("orientation",orientation);
              return;
          }
          super.onRestoreInstanceState(state);
      }
      複製程式碼

9.1.4 去除淡黃色警告優化

  • 淡黃色警告雖然不會造成崩潰,但是作為程式設計師還是要儘量去除淡黃色警告,規範程式碼
    • image

9.1.5 使用註解替代列舉優化

  • 使用註解限定傳入型別
    • 比如,尤其是寫第三方開源庫,對於有些暴露給開發者的方法,需要限定傳入型別是有必要的。舉個例子:
    • 剛開始的程式碼
      /**
       * 設定播放器型別,必須設定
       * 注意:感謝某人建議,這裡限定了傳入值型別
       * 輸入值:111   或者  222
       * @param playerType IjkPlayer or MediaPlayer.
       */
      public void setPlayerType(int playerType) {
          mPlayerType = playerType;
      }
      複製程式碼
    • 優化後的程式碼,有效避免第一種方式開發者傳入值錯誤
      /**
       * 設定播放器型別,必須設定
       * 注意:感謝某人建議,這裡限定了傳入值型別
       * 輸入值:ConstantKeys.IjkPlayerType.TYPE_IJK   或者  ConstantKeys.IjkPlayerType.TYPE_NATIVE
       * @param playerType IjkPlayer or MediaPlayer.
       */
      public void setPlayerType(@ConstantKeys.PlayerType int playerType) {
          mPlayerType = playerType;
      }
      
      /**
       * 通過註解限定型別
       * TYPE_IJK                 IjkPlayer,基於IjkPlayer封裝播放器
       * TYPE_NATIVE              MediaPlayer,基於原生自帶的播放器控制元件
       */
      @Retention(RetentionPolicy.SOURCE)
      public @interface IjkPlayerType {
          int TYPE_IJK = 111;
          int TYPE_NATIVE = 222;
      }
      @IntDef({IjkPlayerType.TYPE_IJK,IjkPlayerType.TYPE_NATIVE})
      public @interface PlayerType{}
      複製程式碼
  • 使用註解替代列舉,程式碼如下所示
    @Retention(RetentionPolicy.SOURCE)
    public @interface ViewStateType {
        int HAVE_DATA = 1;
        int EMPTY_DATA = 2;
        int ERROR_DATA = 3;
        int ERROR_NETWORK = 4;
    }
    複製程式碼

9.1.6 glide加速優化

  • 在畫廊中載入大圖
    • 假如你滑動特別快,glide載入優化就顯得非常重要呢,具體優化方法如下所示
      recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
          @Override
          public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
              super.onScrollStateChanged(recyclerView, newState);
              if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                  LoggerUtils.e("initRecyclerView"+ "恢復Glide載入圖片");
                  Glide.with(ImageBrowseActivity.this).resumeRequests();
              }else {
                  LoggerUtils.e("initRecyclerView"+"禁止Glide載入圖片");
                  Glide.with(ImageBrowseActivity.this).pauseRequests();
              }
          }
      });
      複製程式碼

9.1.7 多渠道打包優化

  • 還在手動打包嗎?嘗試一下python自動化打包吧……
    • 瓦力多渠道打包的Python指令碼測試工具,通過該自動化指令碼,自需要run一下或者命令列執行指令碼即可實現美團瓦力多渠道打包,打包速度很快。配置資訊十分簡單,程式碼中已經註釋十分詳細。可以自定義輸出檔案路徑,可以修改多渠道配置資訊,簡單實用。 專案地址:https://github.com/yangchong211/YCWalleHelper

9.1.8 WebView優化

9.1.9 狀態管理切換優化

  • 以前做法:
    • 直接把這些介面include到main介面中,然後動態去切換介面,後來發現這樣處理不容易複用到其他專案中,而且在activity中處理這些狀態的顯示和隱藏比較亂
    • 利用子類繼承父類特性,在父類中寫切換狀態,但有些介面如果沒有繼承父類,又該如何處理
    • 或者寫一個工具類,動態切換不同的狀態,但還是感覺耦合性實在太強。比如說,現在我有不同的頁面需要展示不同的空頁面狀態,感覺就比較麻煩呢!
  • 現在做法:
    • 讓View狀態的切換和Activity徹底分離開,必須把這些狀態View都封裝到一個管理類中,然後暴露出幾個方法來實現View之間的切換。
    • 在不同的專案中可以需要的View也不一樣,所以考慮把管理類設計成builder模式來自由的新增需要的狀態View。具體案例看我:狀態管理器封裝庫

9.2.0 TrimMemory和LowMemory優化

  • 可以優化什麼?
    • 在 onTrimMemory() 回撥中,應該在一些狀態下清理掉不重要的記憶體資源。對於這些快取,只要是讀進記憶體內的都算,例如最常見的圖片快取、檔案快取等。拿圖片快取來說,市場上,常規的圖片載入庫,一般而言都是三級快取,所以在記憶體吃緊的時候,我們就應該優先清理掉這部分圖片快取,畢竟圖片是吃記憶體大戶,而且再次回來的時候,雖然記憶體中的資源被回收掉了,依然可以從磁碟或者網路上恢復它。
  • 大概的思路如下所示
    • 在lowMemory的時候,呼叫Glide.cleanMemory()清理掉所有的記憶體快取。
    • 在App被置換到後臺的時候,呼叫Glide.cleanMemory()清理掉所有的記憶體快取。
    • 在其它情況的onTrimMemory()回撥中,直接呼叫Glide.trimMemory()方法來交給Glide處理記憶體情況。

9.2.1 輪詢操作優化

  • 什麼叫輪訓請求?
    • 簡單理解就是App端每隔一定的時間重複請求的操作就叫做輪訓請求,比如:App端每隔一段時間上報一次定位資訊,App端每隔一段時間拉去一次使用者狀態等,這些應該都是輪訓請求。比如,電商類專案,某個抽獎活動頁面,隔1分鐘呼叫一次介面,彈出一些獲獎人資訊,你應該某個階段看過這類輪詢操作!
  • 具體優化操作
    • 長連線並不是穩定的可靠的,而執行輪訓操作的時候一般都是要穩定的網路請求,而且輪訓操作一般都是有生命週期的,即在一定的生命週期內執行輪訓操作,而長連線一般都是整個程式生命週期的,所以從這方面講也不太適合。
    • 建議在service中做輪詢操作,輪詢請求介面,具體做法和注意要點,可以直接看該專案程式碼。看app包下的LoopRequestService類即可。
    • 大概思路:當使用者開啟這個頁面的時候初始化TimerTask物件,每個一分鐘請求一次伺服器拉取訂單資訊並更新UI,當使用者離開頁面的時候清除TimerTask物件,即取消輪訓請求操作。

9.2.2 去除重複依賴庫優化

  • 我相信你看到了這裡會有疑問,網上有許多部落格作了這方面說明。但是我在這裡想說,如何查詢自己專案的所有依賴關係樹
    • 注意要點:其中app就是專案mudule名字。 正常情況下就是app!
    gradlew app:dependencies
    複製程式碼
  • 關於依賴關係樹的結構圖如下所示,此處省略很多程式碼
    |    |    |    |    |    |    \--- android.arch.core:common:1.1.1 (*)
    |    |    |    |         \--- com.android.support:support-annotations:26.1.0 -> 28.0.0
    |    +--- com.journeyapps:zxing-android-embedded:3.6.0
    |    |    +--- com.google.zxing:core:3.3.2
    |    |    \--- com.android.support:support-v4:25.3.1
    |    |         +--- com.android.support:support-compat:25.3.1 -> 28.0.0 (*)
    |    |         +--- com.android.support:support-media-compat:25.3.1
    |    |         |    +--- com.android.support:support-annotations:25.3.1 -> 28.0.0
    |    |         |    \--- com.android.support:support-compat:25.3.1 -> 28.0.0 (*)
    |    |         +--- com.android.support:support-core-utils:25.3.1 -> 28.0.0 (*)
    |    |         +--- com.android.support:support-core-ui:25.3.1 -> 28.0.0 (*)
    |    |         \--- com.android.support:support-fragment:25.3.1 -> 28.0.0 (*)
    \--- com.android.support:multidex:1.0.2 -> 1.0.3
    複製程式碼
  • 然後檢視哪些重複jar
    • image
  • 然後修改gradle配置程式碼
    api (rootProject.ext.dependencies["zxing"]){
        exclude module: 'support-v4'
        exclude module: 'appcompat-v7'
    }
    複製程式碼

9.2.3 四種引用優化【靈活運用軟引用和弱引用】

  • 軟引用使用場景
    • 正常是用來處理大圖片這種佔用記憶體大的情況
      • 程式碼如下所示
      Bitmap bitmap = bitmaps.get(position);
      //正常是用來處理圖片這種佔用記憶體大的情況
      bitmapSoftReference = new SoftReference<>(bitmap);
      if(bitmapSoftReference.get() != null) {
          viewHolder.imageView.setImageBitmap(bitmapSoftReference.get());
      }
      //其實看glide底層原始碼可知,也做了相關軟引用的操作
      複製程式碼
    • 這樣使用軟引用好處
      • 通過軟引用的get()方法,取得bitmap物件例項的強引用,發現物件被未回收。在GC在記憶體充足的情況下,不會回收軟引用物件。此時view的背景顯示
      • 實際情況中,我們會獲取很多圖片.然後可能給很多個view展示, 這種情況下很容易記憶體吃緊導致oom,記憶體吃緊,系統開始會GC。這次GC後,bitmapSoftReference.get()不再返回bitmap物件,而是返回null,這時螢幕上背景圖不顯示,說明在系統記憶體緊張的情況下,軟引用被回收。
      • 使用軟引用以後,在OutOfMemory異常發生之前,這些快取的圖片資源的記憶體空間可以被釋放掉的,從而避免記憶體達到上限,避免Crash發生。
  • 弱引用使用場景
    • 弱引用–>隨時可能會被垃圾回收器回收,不一定要等到虛擬機器記憶體不足時才強制回收。
    • 對於使用頻次少的物件,希望儘快回收,使用弱引用可以保證記憶體被虛擬機器回收。比如handler,如果希望使用完後儘快回收,看下面程式碼
    private MyHandler handler = new MyHandler(this);
    private static class MyHandler extends Handler{
        WeakReference<FirstActivity> weakReference;
        MyHandler(FirstActivity activity) {
            weakReference = new WeakReference<>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
            }
        }
    }
    複製程式碼
  • 到底什麼時候使用軟引用,什麼時候使用弱引用呢?
    • 個人認為,如果只是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對於應用的效能更在意,想盡快回收一些佔用記憶體比較大的物件,則可以使用弱引用。
    • 還有就是可以根據物件是否經常使用來判斷。如果該物件可能會經常使用的,就儘量用軟引用。如果該物件不被使用的可能性更大些,就可以用弱引用。

10.元件化部落格

Android元件化開發實踐和案例分享

11.其他介紹

00.關於其他內容介紹

image

01.關於部落格彙總連結

02.關於我的部落格

  • 我的個人站點:www.yczbj.org,www.ycbjie.cn
  • github:https://github.com/yangchong211
  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 簡書:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
  • 開源中國:https://my.oschina.net/zbj1618/blog
  • 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 郵箱:yangchong211@163.com
  • 阿里雲部落格:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
  • segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles
  • 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e

03.其他封裝庫推薦

04.勘誤及提問

  • 如果有疑問或者發現錯誤,可以在相應的 issues 進行提問或勘誤。
  • 如果喜歡或者有所啟發,歡迎star,對作者也是一種鼓勵。轉載麻煩註明出處。請掛上“瀟湘劍雨”的小名!

05.關於LICENSE

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
複製程式碼

專案地址:https://github.com/yangchong211/LifeHelper

相關文章