Android內(nèi)存優(yōu)化操作方法梳理總結(jié)
內(nèi)存泄露
內(nèi)存泄漏就是在當(dāng)前應(yīng)用周期內(nèi)不再使用的對象被GC Roots引用,導(dǎo)致不能回收,使實際可使用內(nèi)存變小,通俗點講,就是無法回收無用對象。這里總結(jié)了實際開發(fā)中常見的一些內(nèi)存泄露的場景示例和解決方案。
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例
該實例的生命周期和應(yīng)用一樣長,非靜態(tài)內(nèi)部類會自動持有外部類的引用,這就導(dǎo)致該靜態(tài)實例一直持有外部類Activity的引用。
class MemoryActivity : AppCompatActivity() {
companion object {
var test: Test? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory)
test = Test()
}
inner class Test {
}
}解決方案:將非靜態(tài)內(nèi)部類改為靜態(tài)內(nèi)部類
class MemoryActivity : AppCompatActivity() {
companion object {
var test: Test? = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory)
test = Test()
}
//kotlin的靜態(tài)內(nèi)部類
class Test {
}
}注冊對象未注銷或資源對象未關(guān)閉
注冊了像BraodcastReceiver,EventBus這種,沒有在頁面銷毀時注銷的話,會引發(fā)泄露問題,所以應(yīng)該在Activity銷毀時及時注銷。
類的靜態(tài)變量引用耗費資源過多的實例
類的靜態(tài)變量生命周期等于應(yīng)用程序的生命周期,若其引用耗資過多的實例,如Context,當(dāng)引用實例需結(jié)束生命周期時,會因靜態(tài)變量的持有而無法被回收,從而出現(xiàn)內(nèi)存泄露,這種情況比較常見的有單例持有context。
class SingleTon private constructor(val context: Context) {
companion object {
private var instance: SingleTon? = null
fun getInstance(context: Context) =
if (instance == null) SingleTon(context) else instance!!
}
}當(dāng)我們在Activity中使用時,當(dāng)Activity銷毀,就會出現(xiàn)內(nèi)存泄露
class MemoryActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory)
SingleTon.getInstance(this)
}
}這種情況可以使用applicationContext,因為Application的生命周期就等于整個應(yīng)用的生命周期
class SingleTon private constructor(context: Context) {
private var context: Context
init {
this.context = context.applicationContext
}
companion object {
private var instance: SingleTon? = null
fun getInstance(context: Context) =
if (instance == null) SingleTon(context) else instance!!
}
}Handler引發(fā)的內(nèi)存泄露
class MemoryActivity : AppCompatActivity() {
private val tag = javaClass.simpleName
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
Log.i(tag, "handleMessage:$msg")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory)
thread(start = true) {
handler.sendEmptyMessageDelayed(1, 10000)
}
}
}當(dāng)Activity被finish時,延遲發(fā)送的消息仍會存活在UI線程的消息隊列中,直到10s后才被處理,這個消息持有handler的引用,由于非靜態(tài)內(nèi)部類或匿名類會隱式持有外部類的引用,handler隱式持有外部類也就是Activity的引用,這個引用會一直存在直到這個消息被處理,所以垃圾回收機制就沒法回收而導(dǎo)致內(nèi)存泄露。
解決方案:靜態(tài)內(nèi)部類+弱引用,靜態(tài)內(nèi)部類不會持有外部類的引用,如需handler內(nèi)調(diào)用外部類Activity的方法的話,可以讓handler持有外部類Activity的弱引用,這樣Activity就不會有泄露風(fēng)險了。
class MemoryActivity : AppCompatActivity() {
companion object {
private const val tag = "uncle"
}
private lateinit var handler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory)
handler = MyHandler(this)
thread(start = true) {
handler.sendEmptyMessageDelayed(1, 10000)
}
}
class MyHandler(activity: Activity) : Handler(Looper.getMainLooper()) {
private val reference = WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (reference.get() != null) {
Log.i(tag, "handleMessage:$msg")
}
}
}
}集合引發(fā)的內(nèi)存泄露
先看個例子,我們定義一個棧,裝著所有的Activity
class GlobalData {
companion object {
val activityStack = Stack<Activity>()
}
}
然后每啟動一個Activity,就把此Activity加進去,這個時候,如果你沒有在Activity銷毀時清掉集合中對應(yīng)的引用,就會出現(xiàn)泄露問題。當(dāng)然,實際開發(fā)中我們不會寫這么差的代碼,這只是簡單提個醒,需要注意一下集合中的一些引用,如果會導(dǎo)致泄露的,記得及時在銷毀時清掉。
class MemoryActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_memory)
GlobalData.activityStack.push(this)
}
}檢測工具
排查內(nèi)存泄露,需要一些工具的支持,這里主要介紹常用的兩個,LeakCanary和Android Studio Profiler。
LeakCanary
一行代碼引入
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
當(dāng)你測試包安裝時,手機上就會有個伴生APP,用來記錄內(nèi)存泄露信息的。

就拿上面集合引發(fā)的泄露例子來說,LeakCanary就會彈出通知并且Leaks APP中顯示內(nèi)存泄露信息,我們以此來定位內(nèi)存泄露問題。



Android Studio Profiler
同樣,我們拿上面集合的泄漏例子來看,首先,我們點擊MEMORY

然后,普通的內(nèi)存問題選擇Capture heap dump就行了

點擊Record,就會抓取一段時間的內(nèi)存分配信息

Leaks就是記錄內(nèi)存泄漏的,然后我們點擊進去,就可以看到具體類位置了

再點擊進去具體的類,就可以看到泄漏的原因啦

內(nèi)存溢出
Android系統(tǒng)中每個應(yīng)用程序可以向系統(tǒng)申請一定的內(nèi)存,當(dāng)申請的內(nèi)存不夠用的時候,就會產(chǎn)生內(nèi)存溢出,俗稱OOM,全稱Out Of Memory,就是內(nèi)存用完了。在實際開發(fā)中,出現(xiàn)這種現(xiàn)象通常是因為內(nèi)存泄露太多或大圖加載問題,內(nèi)存泄露上面已經(jīng)講了,那么,下面就主要講講圖片的優(yōu)化吧!
Bitmap優(yōu)化
(1)及時回收Bitmap內(nèi)存,這時可能有人就要問了,Android有自己的垃圾回收機制,為什么還要我們?nèi)セ厥漳??因為生成Bitmap最終是通過JNI方法實現(xiàn)的,也就是說,Bitmap的加載包含兩部分的內(nèi)存區(qū)域,一是Java部分,一是C部分。Java部分會自動回收,但是C部分不會,所以需要調(diào)用recycle來釋放C部分的內(nèi)存。那如果不調(diào)用就一定會出現(xiàn)泄露嗎?那也不是的,Android每個應(yīng)用都在獨立的進程,進程被關(guān)掉的話,內(nèi)存也就都被釋放了。
if (bitmap != null && !bitmap.isRecycled) {
bitmap.recycle()
bitmap = null
}(2)捕獲異常,Bitmap在使用的時候,最好捕獲一下OutOfMemoryError以免crash掉,你還可以設(shè)置一個默認(rèn)的圖片。
var bitmap: Bitmap? = null
try {
bitmap = BitmapFactory.decodeFile(filePath)
imageView.setImageBitmap(bitmap)
} catch (e: OutOfMemoryError) {
//捕獲異常
}
if (bitmap == null) {
imageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.picture))
}(3)壓縮,對于分辨率比較高的圖片,我們應(yīng)該加載一個縮小版,這里采用的是采樣率壓縮法。
val options = BitmapFactory.Options()
//設(shè)置為true可以讓解析方法禁止為bitmap分配內(nèi)存,返回null,同時能獲取到長寬值,從而根據(jù)情況進行壓縮
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(resources, R.drawable.large_picture, options)
val imgHeight = options.outHeight
val imgWidth = options.outWidth
//通過改變inSampleSize的值來壓縮圖片
var inSampleSize = 1
//imgWidth為圖片的寬,viewWidth為實際控件的寬
if (imgHeight > viewHeight || imgWidth > viewWidth) {
val heightRatio = round(imgHeight / viewHeight.toFloat()).toInt()
val widthRatio = round(imgWidth / viewWidth.toFloat()).toInt()
//選擇最小比率作為inSampleSize的值,可保證最終圖片的寬高一定大于等于目標(biāo)的寬高
inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio
}
options.inSampleSize = inSampleSize
//計算完后inJustDecodeBounds重置為false
options.inJustDecodeBounds = false
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.large_picture, options)
imageView.setImageBitmap(bitmap)如果程序中的圖片是本地資源或者是自己服務(wù)器上的,那這個大小我們可以自行調(diào)整,只要注意圖片不要太大,及時回收Bitmap,就能避免OOM的發(fā)生。如果圖片來源是外界,這個時候就要特別注意了,可以采用壓縮圖片或捕獲異常,避免OOM的產(chǎn)生而導(dǎo)致程序崩潰。
內(nèi)存抖動
頻繁地創(chuàng)建對象,會導(dǎo)致內(nèi)存抖動,最終可能會導(dǎo)致卡頓或OOM,因為大量臨時對象頻繁創(chuàng)建會導(dǎo)致內(nèi)存碎片,當(dāng)需要分配內(nèi)存時,雖然總體上還有剩余內(nèi)存,但由于這些內(nèi)存不連續(xù),無法整塊分配,系統(tǒng)會視為內(nèi)存不夠,故導(dǎo)致OOM。
常見場景為大循環(huán)中創(chuàng)建對象,自定義View的onDraw方法中創(chuàng)建對象,因為屏幕繪制會頻繁調(diào)用onDraw方法。我們可以將這些操作放在循環(huán)外或onDraw方法外,避免頻繁創(chuàng)建對象。
到此這篇關(guān)于Android內(nèi)存優(yōu)化操作方法梳理總結(jié)的文章就介紹到這了,更多相關(guān)Android內(nèi)存優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Studio生成 Flutter 模板代碼技巧詳解
這篇文章主要為大家介紹了Android Studio生成 Flutter 模板代碼技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
Android中Activity和Fragment傳遞數(shù)據(jù)的兩種方式
本篇文章主要介紹了Android中Activity和Fragment傳遞數(shù)據(jù)的兩種方式,非常具有實用價值,需要的朋友可以參考下2017-09-09
android sdk安裝及開發(fā)環(huán)境部署
本文給大家詳細講解了android sdk安裝方法以及android開發(fā)環(huán)境部署方法,非常的細致全面,有需要的小伙伴務(wù)必詳細研究下。2015-11-11
android ListView和ProgressBar(進度條控件)的使用方法
這篇文章主要介紹了android ListView控件的使用方法和ProgressBar(進度條控件)的使用方法,代碼大家可以參考使用2013-11-11
Android登陸界面實現(xiàn)清除輸入框內(nèi)容和震動效果
這篇文章主要介紹了Android登陸界面實現(xiàn)清除輸入框內(nèi)容和震動效果,感興趣的小伙伴們可以參考一下2015-12-12

