Android內存優(yōu)化操作方法梳理總結
內存泄露
內存泄漏就是在當前應用周期內不再使用的對象被GC Roots引用,導致不能回收,使實際可使用內存變小,通俗點講,就是無法回收無用對象。這里總結了實際開發(fā)中常見的一些內存泄露的場景示例和解決方案。
非靜態(tài)內部類創(chuàng)建靜態(tài)實例
該實例的生命周期和應用一樣長,非靜態(tài)內部類會自動持有外部類的引用,這就導致該靜態(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)內部類改為靜態(tà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)內部類 class Test { } }
注冊對象未注銷或資源對象未關閉
注冊了像BraodcastReceiver,EventBus這種,沒有在頁面銷毀時注銷的話,會引發(fā)泄露問題,所以應該在Activity銷毀時及時注銷。
類的靜態(tài)變量引用耗費資源過多的實例
類的靜態(tài)變量生命周期等于應用程序的生命周期,若其引用耗資過多的實例,如Context,當引用實例需結束生命周期時,會因靜態(tài)變量的持有而無法被回收,從而出現(xiàn)內存泄露,這種情況比較常見的有單例持有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!! } }
當我們在Activity中使用時,當Activity銷毀,就會出現(xiàn)內存泄露
class MemoryActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_memory) SingleTon.getInstance(this) } }
這種情況可以使用applicationContext,因為Application的生命周期就等于整個應用的生命周期
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ā)的內存泄露
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) } } }
當Activity被finish時,延遲發(fā)送的消息仍會存活在UI線程的消息隊列中,直到10s后才被處理,這個消息持有handler的引用,由于非靜態(tài)內部類或匿名類會隱式持有外部類的引用,handler隱式持有外部類也就是Activity的引用,這個引用會一直存在直到這個消息被處理,所以垃圾回收機制就沒法回收而導致內存泄露。
解決方案:靜態(tài)內部類+弱引用,靜態(tài)內部類不會持有外部類的引用,如需handler內調用外部類Activity的方法的話,可以讓handler持有外部類Activity的弱引用,這樣Activity就不會有泄露風險了。
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ā)的內存泄露
先看個例子,我們定義一個棧,裝著所有的Activity
class GlobalData { companion object { val activityStack = Stack<Activity>() } }
然后每啟動一個Activity,就把此Activity加進去,這個時候,如果你沒有在Activity銷毀時清掉集合中對應的引用,就會出現(xiàn)泄露問題。當然,實際開發(fā)中我們不會寫這么差的代碼,這只是簡單提個醒,需要注意一下集合中的一些引用,如果會導致泄露的,記得及時在銷毀時清掉。
class MemoryActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_memory) GlobalData.activityStack.push(this) } }
檢測工具
排查內存泄露,需要一些工具的支持,這里主要介紹常用的兩個,LeakCanary和Android Studio Profiler。
LeakCanary
一行代碼引入
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
當你測試包安裝時,手機上就會有個伴生APP,用來記錄內存泄露信息的。
就拿上面集合引發(fā)的泄露例子來說,LeakCanary就會彈出通知并且Leaks APP中顯示內存泄露信息,我們以此來定位內存泄露問題。
Android Studio Profiler
同樣,我們拿上面集合的泄漏例子來看,首先,我們點擊MEMORY
然后,普通的內存問題選擇Capture heap dump就行了
點擊Record,就會抓取一段時間的內存分配信息
Leaks就是記錄內存泄漏的,然后我們點擊進去,就可以看到具體類位置了
再點擊進去具體的類,就可以看到泄漏的原因啦
內存溢出
Android系統(tǒng)中每個應用程序可以向系統(tǒng)申請一定的內存,當申請的內存不夠用的時候,就會產生內存溢出,俗稱OOM,全稱Out Of Memory,就是內存用完了。在實際開發(fā)中,出現(xiàn)這種現(xiàn)象通常是因為內存泄露太多或大圖加載問題,內存泄露上面已經講了,那么,下面就主要講講圖片的優(yōu)化吧!
Bitmap優(yōu)化
(1)及時回收Bitmap內存,這時可能有人就要問了,Android有自己的垃圾回收機制,為什么還要我們去回收呢?因為生成Bitmap最終是通過JNI方法實現(xiàn)的,也就是說,Bitmap的加載包含兩部分的內存區(qū)域,一是Java部分,一是C部分。Java部分會自動回收,但是C部分不會,所以需要調用recycle來釋放C部分的內存。那如果不調用就一定會出現(xiàn)泄露嗎?那也不是的,Android每個應用都在獨立的進程,進程被關掉的話,內存也就都被釋放了。
if (bitmap != null && !bitmap.isRecycled) { bitmap.recycle() bitmap = null }
(2)捕獲異常,Bitmap在使用的時候,最好捕獲一下OutOfMemoryError以免crash掉,你還可以設置一個默認的圖片。
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)壓縮,對于分辨率比較高的圖片,我們應該加載一個縮小版,這里采用的是采樣率壓縮法。
val options = BitmapFactory.Options() //設置為true可以讓解析方法禁止為bitmap分配內存,返回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的值,可保證最終圖片的寬高一定大于等于目標的寬高 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)
如果程序中的圖片是本地資源或者是自己服務器上的,那這個大小我們可以自行調整,只要注意圖片不要太大,及時回收Bitmap,就能避免OOM的發(fā)生。如果圖片來源是外界,這個時候就要特別注意了,可以采用壓縮圖片或捕獲異常,避免OOM的產生而導致程序崩潰。
內存抖動
頻繁地創(chuàng)建對象,會導致內存抖動,最終可能會導致卡頓或OOM,因為大量臨時對象頻繁創(chuàng)建會導致內存碎片,當需要分配內存時,雖然總體上還有剩余內存,但由于這些內存不連續(xù),無法整塊分配,系統(tǒng)會視為內存不夠,故導致OOM。
常見場景為大循環(huán)中創(chuàng)建對象,自定義View的onDraw方法中創(chuàng)建對象,因為屏幕繪制會頻繁調用onDraw方法。我們可以將這些操作放在循環(huán)外或onDraw方法外,避免頻繁創(chuàng)建對象。
到此這篇關于Android內存優(yōu)化操作方法梳理總結的文章就介紹到這了,更多相關Android內存優(yōu)化內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android遞歸方式刪除某文件夾下的所有文件(.mp3文件等等)
以刪除為例,當然,對于遍歷某文件夾下的所有文件均可用這個方法。如搜索.mp3文件等,具體實現(xiàn)如下,感興趣的朋友可以參考下哈2013-06-06Android使用Jetpack Compose開發(fā)零基礎起步教程
Jetpack Compose是用于構建原生Android UI的現(xiàn)代工具包。Jetpack Compose使用更少的代碼,強大的工具和直觀的Kotlin API,簡化并加速了Android上的UI開發(fā)2023-04-04Android使用CardView作為RecyclerView的Item并實現(xiàn)拖拽和左滑刪除
這篇文章主要介紹了Android使用CardView作為RecyclerView的Item并實現(xiàn)拖拽和左滑刪除,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11Android自定義view實現(xiàn)雪花特效實例代碼
實現(xiàn)雪花的效果其實也可以通過自定義View的方式來實現(xiàn)的,而且操作上也相對簡單一些,下面這篇文章主要給大家介紹了關于Android自定義view實現(xiàn)雪花特效的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-12-12Android 邊播邊緩存的實現(xiàn)(MP4 未加密m3u8)
這篇文章主要介紹了Android 邊播邊緩存的實現(xiàn)(MP4 未加密m3u8),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11