Adnroid性能優(yōu)化之內(nèi)存優(yōu)化

前言

?開發(fā)程序過程中常常涉及到內(nèi)存的申請以及回收過程,由于表現(xiàn)形式不明顯而且Java有自動垃圾回收機制,普遍情況下不會過度關(guān)注內(nèi)存,容易疏漏導(dǎo)致拋出異常。同時OOM等內(nèi)存問題所拋出的堆棧信息有可能不是問題的直接原因,因此還存在排查難等問題。


1 分類

  • 內(nèi)存抖動:短時間內(nèi)創(chuàng)建大量新生代對象導(dǎo)致GC頻繁,通過觀察Profiler Memory發(fā)現(xiàn)內(nèi)存呈鋸齒狀。
  • 內(nèi)存泄漏:已分配內(nèi)存由于某種原因未能被回收,導(dǎo)致可用內(nèi)存逐漸減少。例如,界面被銷毀,但是Activity還被持有引用,導(dǎo)致Activity無法被回收。
  • 內(nèi)存溢出:無法申請到所需的內(nèi)存空間時。例如,加載圖片過大導(dǎo)致發(fā)生程序異常,提示OutOfMemory。

2 工具使用

  • Profiler
  • Memory Analyzer(和Proiler差不多,不使用)
  • LeakCanary

3 內(nèi)存抖動

?首先使用Profiler進行排查,然后再根據(jù)記錄抖動位置的代碼進行排查。以下面這個方法為例:

/**
     *  排序后打印二維數(shù)組,一行行打印
     */
    fun imPrettySureSortingIsFree() {
        val dimension = 300
        val lotsOfInts =
            Array(dimension) { IntArray(dimension) }
        val randomGenerator = Random()
        for (i in lotsOfInts.indices) {
            for (j in lotsOfInts[i].indices) {
                lotsOfInts[i][j] = randomGenerator.nextInt()
            }
        }
        for (i in lotsOfInts.indices) {
            var rowAsStr = ""
            //排序
            val sorted = getSorted(lotsOfInts[i])
            //拼接打印
            for (j in lotsOfInts[i].indices) {
                rowAsStr += sorted[j]
                if (j < lotsOfInts[i].size - 1) {
                    rowAsStr += ", "
                }
            }
        }
    }

    fun getSorted(input: IntArray): IntArray {
        val clone = input.clone()
        Arrays.sort(clone)
        return clone
    }

1.運行后觀察Profiler發(fā)現(xiàn)內(nèi)存GC頻繁,我們可以點擊record記錄下內(nèi)存的變化,查看這段時間的內(nèi)存分配情況。


2.前面說過造成內(nèi)存抖動的原因,因此可以根據(jù)內(nèi)存分配的數(shù)量進行分析。點擊Allocations進行排序之后,發(fā)現(xiàn)char[]分配的對象是最多的,因此char[]成為了首先懷疑的對象。


3.點擊列表的實例查看char[]分配回調(diào)棧,通過對比發(fā)現(xiàn)內(nèi)容都一致,因此可以基本斷定是該實例導(dǎo)致內(nèi)存抖動。

  1. 最后發(fā)現(xiàn)可能是MemoryPerformanceActivity中Oncreate的handleMessage或者imPrettySureSortingIsFree方法引起的抖動,我們就可以點擊右鍵選擇Jump to spurce跳轉(zhuǎn),并解決問題。

4 內(nèi)存泄漏

?導(dǎo)致內(nèi)存泄漏的原因有很多,例如Cursor、IO操作未關(guān)閉,Bitmap、Context未回收等等。我們可以通過Profler Memory堆轉(zhuǎn)儲功能進行內(nèi)存泄漏分析。以下面方法為例:

class MemoryPerformanceActivity : AppCompatActivity(), CallBack {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory_performance)
        val imageView: ImageView = findViewById(R.id.imageView)
        val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
        imageView.setImageBitmap(bitmap)
        CallBackManager.addCallBack(this)
    }

    override fun mack() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}
object CallBackManager {
    private var mCallBacks = arrayListOf<CallBack>()
    fun addCallBack(callBack: CallBack) {
        mCallBacks.add(callBack)
    }
    fun removeCallBack(callBack: CallBack) {
        mCallBacks.remove(callBack)
    }
}

1.來回跳轉(zhuǎn)數(shù)次到MemoryPerformanceActivity,通過Profiler觀察內(nèi)存的變化,主要是查看Activity結(jié)束時Total是否有減少,我們可以如果占用內(nèi)存越來越高,則可能發(fā)生內(nèi)存泄露。


2.猜測可能發(fā)生內(nèi)存泄漏之后,我們可點擊強制GC去除引用,再點擊Dump java heap,即可得到hprof文件進行分析。


3.我們可以點擊Arrange by packge按包名進行排序或者點擊Filter搜索myApplication。通過觀察可以發(fā)現(xiàn)強制GC之后MemoryPerformanceActivity仍然存在6個對象。我們想要看到的是1個對象,因此這并不是我們預(yù)期想要看到的。


4.點擊MemoryPerformanceActivity查看實例,可以看到每個Aactivity的實際內(nèi)存分配為94000左右,引用深度Depth為3,再通過References查看引用,可以看出來是mCallBacks持有了6個Activity的引用,其Retained Size的大小為570803。最后看到shadow$_klass_in_CallBackManager,發(fā)現(xiàn)是CallBackManager引用了mCallBacks,點擊右鍵選擇Jump to source跳轉(zhuǎn)到CallBackManager進行問題分析。


5.通過分析我們發(fā)現(xiàn)CallBackManager 是個靜態(tài)類,持有的對象與App生命周期一樣長,因此需要手動將CallBack移除。

  override fun onDestroy() {
        super.onDestroy()
        CallBackManager.removeCallBack(this)
    }

5 內(nèi)存溢出

?當申請不到需要的內(nèi)存時就會發(fā)生內(nèi)存溢出(OOM),在開發(fā)過程中我們常見的OOM就有Bitmap加載不合理圖片造成的。我們可以在線下通過ARTHoot去監(jiān)測出不合理的圖片,在線下就將問題暴露出來。
?從錯誤堆棧信息不一定能看出內(nèi)存溢出的準確問題點,因為內(nèi)存溢出可能只是表象,造成內(nèi)存溢出的原因有可能是因為內(nèi)存泄漏,導(dǎo)致可用內(nèi)存越來越少,最后分配不到我們所需的空間,導(dǎo)致內(nèi)存溢出。


6 LeakCanary

?leakcanary是由square開源的框架,可以幫助我們快速發(fā)現(xiàn)內(nèi)存泄漏,減少OOM。集成很簡單,只需要下面這一步,原理就是利用了Provider的Oncreate比Application的生命周期還要早,內(nèi)部實現(xiàn)了Provider并進行框架初始化。

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
}

集成之后Leakcanary就會自動幫我們收集內(nèi)存泄漏信息。

總結(jié)

  • MAT和Profiler使用上差不多,因此建議使用Profiler即可,發(fā)現(xiàn)問題可直接跳轉(zhuǎn)到相應(yīng)的代碼位置,提高排查效率。
  • 在開發(fā)初期集成LeakCanary去收集內(nèi)存泄漏信息,早發(fā)現(xiàn)早解決。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容