最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android?內(nèi)存優(yōu)化知識點(diǎn)梳理總結(jié)

 更新時間:2022年06月16日 09:25:07   作者:??自動化BUG制造器????  
這篇文章主要介紹了Android?內(nèi)存優(yōu)化知識點(diǎn)梳理總結(jié),Android?操作系統(tǒng)給每個進(jìn)程都會分配指定額度的內(nèi)存空間,App?使用內(nèi)存來進(jìn)行快速的文件訪問交互,長時間如此便需要優(yōu)化策略,文章分享優(yōu)化知識點(diǎn)總結(jié),需要的朋友可以參考一下

前言:

Android 操作系統(tǒng)給每個進(jìn)程都會分配指定額度的內(nèi)存空間,App 使用內(nèi)存來進(jìn)行快速的文件訪問交互。例如展示網(wǎng)絡(luò)圖片時,就是通過把網(wǎng)絡(luò)圖片下載到內(nèi)存中展示,如果需要保存到本地,再從內(nèi)存中保存到磁盤空間中。

RAM 和 ROM

手機(jī)一般有兩種存儲介質(zhì),一個是 RAM ,我們常說的內(nèi)存,也稱之為運(yùn)行內(nèi)存;另一個是 ROM ,即磁盤空間。 RAM 的訪問速度一般會比 ROM 快,它是即插即用,斷電會抹除所有數(shù)據(jù),RAM 越大,可同時操作的數(shù)據(jù)就越多;ROM 是外部存儲空間,相當(dāng)于電腦的硬盤,主要是用來存儲本地?cái)?shù)據(jù)的。

App 運(yùn)行時,會被加載到 RAM 中,又因?yàn)?App 所在進(jìn)程會分配指定額度的空間,所以 App 的內(nèi)存空間是有限的,內(nèi)存的大小對 App 性能及正常運(yùn)行都會有很大的影響。 當(dāng) App 所分配的內(nèi)存空間不足時,會拋出 OOM 。所以對運(yùn)行中的 App 的內(nèi)存的優(yōu)化就顯得尤為重要。

常見內(nèi)存問題

常見的內(nèi)存問題包括:

  • 內(nèi)存泄漏:因?yàn)?Java 對象無法被正常回收,如果長期運(yùn)行程序,就會造成大量的無用對象占用內(nèi)存空間,最終導(dǎo)致 OOM。
  • 內(nèi)存抖動:頻繁的創(chuàng)建對象,當(dāng)對象數(shù)據(jù)到達(dá)一定程度會造成 GC ,如果短時間內(nèi)頻繁的 GC 就會造成 App 卡頓的現(xiàn)象,這個就叫內(nèi)存抖動。
  • 內(nèi)存溢出:當(dāng) App 申請內(nèi)存空間時,沒有足夠的內(nèi)存空間供其使用,就會導(dǎo)致內(nèi)存溢出,即 Out Of Memory。

內(nèi)存溢出

內(nèi)存溢出(Out Of Memory,簡稱OOM)是指應(yīng)用系統(tǒng)中存在無法回收的內(nèi)存或使用的內(nèi)存過多,最終使得程序運(yùn)行要用到的內(nèi)存大于能提供的最大內(nèi)存。此時 App 就運(yùn)行不了,系統(tǒng)會提示內(nèi)存溢出,拋出異常。

所以避免 OOM 的辦法就是解決內(nèi)存泄漏問題,或盡量在代碼中節(jié)約使用內(nèi)存兩種思路。

內(nèi)存泄漏

內(nèi)存泄漏在 Android 中就是在當(dāng)前App 的生命周期內(nèi)不再使用的對象被GC Roots引用,導(dǎo)致不能回收,使實(shí)際可使用內(nèi)存變小。 需要注意的是,內(nèi)存泄漏問題的出現(xiàn),是和生命周期有關(guān)系的,從生命周期的角度考慮,就是生命周期短的對象被生命周期長的 GC Roots 對象持有引用,從而導(dǎo)致生命周期短的對象在該被回收的時候,無法被正確回收,該對象長期存活,但又毫無用處,白白地占用了內(nèi)存空間。當(dāng)這種對象過多時,就會造成 OOM 。

常見內(nèi)存泄漏場景

無法回收無用對象的場景,可以統(tǒng)一理解為發(fā)生了內(nèi)存泄漏,常見的 case 有:

  • 資源文件未關(guān)閉/回收
  • 注冊對象未注銷
  • 靜態(tài)變量持有數(shù)據(jù)對象
  • 單例造成內(nèi)存泄漏
  • 非靜態(tài)內(nèi)部類的實(shí)例持有外部類引用
  • Handler
  • 集合對象中的對象未釋放
  • WebView 內(nèi)存泄漏
  • View 的生命周期大于容器的生命周期

常見的諸如資源文件未關(guān)閉/為回收、注冊對象未注銷,導(dǎo)致觀察者一致持有注冊對象的引用,從而無法正?;厥兆缘膶ο?。這里對其他幾種場景進(jìn)行詳細(xì)的說明。

靜態(tài)變量或單例持有對象

在 JVM 規(guī)范中,靜態(tài)變量屬于 GC Root 其中的一種,一般情況下它的生命周期都會比較長,所以如果一個對象的某個屬性被靜態(tài)變量持有了引用,就會導(dǎo)致該屬性實(shí)例無法正常被回收。

以簡單的示例代碼說明:

class TestC {
    companion object {
        var leak: Any? = null
    }
}
class LeakCanaryActivity : ComponentActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent { LeakCanaryPage(actions()) }
    staticOOM()
	}
	private fun staticOOM() {
    Toast.makeText(this, "static own Context", Toast.LENGTH_SHORT).show()
    TestC.leak = this
	}
}

當(dāng)我們打開這個LeakCanaryActivity后,返回上一個 Activity,此時查看 Profiler 排查內(nèi)存泄漏的內(nèi)容:

同樣的道理,單例模式一般也是全局的生命周期且唯一的對象,如果被單例持有也會導(dǎo)致一樣的問題。

object TestB {
    var leak: Any? = null
}
// 修改 LeakCanaryActivity 的 staticOOM 方法
	private fun staticOOM() {
    Toast.makeText(this, "static own Context", Toast.LENGTH_SHORT).show()
    TestB.leak = this
	}

非靜態(tài)內(nèi)部類的實(shí)例生命周期比外部類更長導(dǎo)致的內(nèi)存泄漏

非靜態(tài)內(nèi)部類一般持有對外部類實(shí)例的引用,這個可以通過查看 class 文件發(fā)現(xiàn),內(nèi)部類的構(gòu)造方法一般需要一個外部類類型的參數(shù)。所以如果一個內(nèi)部類對象,生命周期更久的話就會造成內(nèi)存泄漏。 這里一個比較明顯的例子是多線程操作內(nèi)部類對象時,外部類的生命周期已經(jīng)結(jié)束時,因?yàn)閮?nèi)部類實(shí)例持有外部類的引用,導(dǎo)致外部類實(shí)例無法被正常回收:

class LeakCanaryActivity : ComponentActivity() {
	// ... 
	// 執(zhí)行這個方法
	private fun innerClassOOM() {
    Toast.makeText(this, "inner leak", Toast.LENGTH_SHORT).show()
    val inner = InnerLeak()
    Thread(inner).start()
	  finish()
	}
	// 內(nèi)部類
	inner class InnerLeak: Runnable {
    override fun run() {
        Thread.sleep(15000)
    }
	}
}

當(dāng)我們打開一個 Activity 后,立刻創(chuàng)建一個新的線程執(zhí)行內(nèi)部類,然后立刻關(guān)閉自身,此時因?yàn)?InnerLeak 仍在子線程中,子線程在 sleep ,導(dǎo)致,外部類生命周期已經(jīng)結(jié)束(調(diào)用了 finish),內(nèi)部類對象 inner 仍持有外部類LeakCanaryActivity的引用。 除了這種內(nèi)部類的形式,也可以用匿名內(nèi)部類的形式來寫,都會導(dǎo)致內(nèi)存泄漏。 另一方面,不光是多線程的場景,如果內(nèi)部類對象被靜態(tài)變量持有引用也是一樣的效果,因?yàn)樗麄兌汲钟辛藘?nèi)部類的引用,導(dǎo)致內(nèi)部類的生命周期比外部類的生命周期更長。

Handler 導(dǎo)致的內(nèi)存泄漏

通過 Handler 發(fā)送消息時,消息對象 Message 本身會持有 Handler 對象:

// Handler#sendMessage(Message) 會執(zhí)行到 enqueueMessage 方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
	  // 這里把 handler 自身保存到了 Message 的 target 屬性中了
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

sendMessage 方法內(nèi)部調(diào)用到enqueueMessage(MessageQueue, Message, long)時,會把 Handler 對象自身賦值到 Message 的 target 上,這樣 message 就知道去找哪個 Handler 執(zhí)行handleMessage(msg: Message)方法。也是因?yàn)檫@個持有,導(dǎo)致了如果消息沒有立刻被執(zhí)行,就會一直持有 Handler 對象,此時如果關(guān)閉 Activity ,就會導(dǎo)致內(nèi)存泄漏。

原因是 Handler 以匿名內(nèi)部類或內(nèi)部類的形式聲明并創(chuàng)建的,會持有外部 Activity 的引用。從而導(dǎo)致持有關(guān)系是:

Message -> Handler -> Activity

實(shí)現(xiàn) Handler 內(nèi)存泄漏的代碼:

// in LeakCanaryActivity
private fun handlerOOM() {
    val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            if (msg.what == 12)
            Toast.makeText(this@LeakCanaryActivity, "handler executed", Toast.LENGTH_SHORT).show()
        }
    }
    Thread {
        handler.sendMessageDelayed(Message().apply { what = 12 }, 10000)
    }.start()
}

操作邏輯是,在 LeakCanaryActivity 中調(diào)用這個方法后,立刻 finish LeakCanaryActivity ,然后查看內(nèi)存泄漏情況:

postDelayed 導(dǎo)致的內(nèi)存泄漏

postDelayed 實(shí)際上是把 Runnable 封裝成了一個 Message 對象,傳入的 Runnable 參數(shù)被賦值給了 Message 的 callback :

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

而最終執(zhí)行邏輯的方法都是 sendMessageDelayed(Message, long),所以和 sendMessage 一樣都會導(dǎo)致內(nèi)存泄漏。與之不同的是,postDelayed的泄漏會多一個Message#callback因?yàn)?在調(diào)用postDelayed時,第一個是個匿名內(nèi)部類對象,多了一個引用。

handler.postDelayed(object : Runnable {
    override fun run() {
        Log.d(TAG, "postdelay done")
    }
}, 10000)

View 的生命周期大于 Activity 時導(dǎo)致的內(nèi)存泄漏

一個極其簡單的內(nèi)存泄漏場景是,當(dāng)我在一個 Activity 內(nèi)多次彈出 Toast 時,立刻關(guān)閉當(dāng)前 Activity ,就會導(dǎo)致內(nèi)存泄漏的情況出現(xiàn):

// in LeakCanaryActivity
private fun toastOOM() {
    Toast.makeText(this, "toast leak", Toast.LENGTH_SHORT).show()
}

操作步驟:將上面的方法設(shè)置在某個點(diǎn)擊事件中,快速連續(xù)點(diǎn)擊幾次,然后立刻關(guān)閉當(dāng)前 Activity ,查看 Profiler:

集合中的對象未釋放導(dǎo)致內(nèi)存泄漏

最常見的場景是觀察者模式,觀察者模式中注冊一些觀察者對象,一般是保存到一個全局的集合中,如果觀察者對象在釋放時不及時注銷,就會造成內(nèi)存泄漏:

object LeakCollection {
	val list = ArrayList<Any>()
}

class LeakCanaryActivity : ComponentActivity() {
	// ... 
	private fun collectionOOM() {
    LeakCollection.list.add(this)
	}
}

操作步驟:在 LeakCanaryActivity 內(nèi)調(diào)用collectionOOM() ,然后立刻 finish 。

最常見的解決辦法就是在 Activity 的 destroy 時,從 list 清除自身的引用。

WebView 導(dǎo)致的內(nèi)存泄漏

網(wǎng)上都說 WebView 會導(dǎo)致內(nèi)存泄漏。通過 Profiler 直接查看并沒有明顯的一個 Leaks 提示。那么如何排查這個內(nèi)存泄露呢?

一個思路是參照對比實(shí)驗(yàn):

  • 對照組 A :NoLeakActivity,一個空的 Activity,里面沒有任何內(nèi)容。
  • 對照組 B :LeakWebViewActivity, 一個包含 WebView 的 Activity 。

在同一個 Root Activity 中分別打開 A 和 B ,通過對比內(nèi)存變化,來證明 WebView 是否真的造成了內(nèi)存泄漏。

首先是打開了 NoLeakActivity, 并沒有明顯的內(nèi)存變化。

然后返回到 LeakCannaryActivity ,內(nèi)存還是沒有變化。接著打開 LeakWebViewActivity ,發(fā)現(xiàn)內(nèi)存明顯上升,主要上升在 Native 、Others 和 Graphics 。 Graphics 可以理解,因?yàn)?loadUrl 失敗了會顯示一個失敗頁面,其中有個 icon 圖片,所以主要分析的點(diǎn)是 Native 和 Others 。

然后返回到 LeakCanaryActivity, 內(nèi)存基本沒有變化。

為了證明,不是因?yàn)?NoLeakActivity 先打開,LeakWebViewActivity 后打開,所以內(nèi)存中會有多余的 NoLeakActivity 相關(guān)的內(nèi)存占用,我們再次打開 NoLeakActivity ,再返回,內(nèi)存仍無明顯變化。

所以,基本上可以證明,WebView 沒有隨著 Activity 的銷毀而被回收。

但是如何解決這種情況呢?這個問題值得后續(xù)仔細(xì)研究一下。但目前網(wǎng)上的各種奇怪的解決方案(例如開啟一個單獨(dú)的進(jìn)程)并不是合理的辦法。 一個說法是,在 xml 里面是有 WebView 會出現(xiàn)內(nèi)存泄漏,但是如果通過 addView 的形式去使用不會造成,以下是通過 addView 的形式添加 一個 WebView 對象的內(nèi)存變化。

而這是通過 XML 的形式使用 WebView 的內(nèi)存變化。

兩種方法好像并沒有什么區(qū)別,但有用的一點(diǎn)是,這里的內(nèi)存變化,主要體現(xiàn)在 Native 上,證明 WebView 組件,會在 Native 層面生成一些內(nèi)容。

這個部分的分析,后續(xù)可以再深入研究。從應(yīng)用層面來看,WebView 并沒有直接觸發(fā)再 Java heap 上的內(nèi)存泄漏。而是更底層的 Native heap 中。

另外需要注意的一點(diǎn)是,通過 LeakCanary 并不能精準(zhǔn)的檢測到內(nèi)存泄漏,還是得用 Profiler。

內(nèi)存抖動

短時間內(nèi)頻繁創(chuàng)建對象,導(dǎo)致虛擬機(jī)頻繁觸發(fā)GC操作,頻繁的 GC 會導(dǎo)致畫面卡頓。

解決方案

  • 盡量避免在循環(huán)體內(nèi)創(chuàng)建對象,應(yīng)該把對象創(chuàng)建移到循環(huán)體外。
  • 注意自定義 View 的 onDraw() 方法會被頻繁調(diào)用,所以在這里面不應(yīng)該頻繁的創(chuàng)建對象。
  • 當(dāng)需要大量使用 Bitmap 的時候,試著把它們緩存在數(shù)組中實(shí)現(xiàn)復(fù)用。
  • 對于能夠復(fù)用的對象,同理可以使用對象池將它們緩存起來。

其他優(yōu)化點(diǎn)

基本上減少內(nèi)存優(yōu)化的其他思路就是復(fù)用和壓縮資源。

  • 圖片資源過大,進(jìn)行縮放處理。
  • 減少不必要的內(nèi)存開銷:一些基本數(shù)據(jù)類型的包裝類,例如 Integer 占用 16 個字節(jié),而 int 占用 4 個字節(jié),所以盡量避免使用自動裝箱的類。
  • 對象和資源進(jìn)行復(fù)用。
  • 選擇更合適的數(shù)據(jù)結(jié)構(gòu),避免數(shù)據(jù)結(jié)構(gòu)分配過大導(dǎo)致的內(nèi)存浪費(fèi)。
  • 使用int 枚舉或 String 枚舉代替枚舉類型 ,但枚舉類型也會有比前者更好的特性,需要酌情使用。
  • 使用 LruCache 等緩存策略。
  • App 內(nèi)存過低時主動清理。

App 內(nèi)存過低時主動清理

實(shí)現(xiàn) Application 中的 onTrimMemory/onLowMemory 方法去釋放掉圖片緩存、靜態(tài)緩存來自保。

class BaseApplication: Application() {
    override fun onLowMemory() {
        super.onLowMemory()
    }
    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
    }
}

到此這篇關(guān)于Android 內(nèi)存優(yōu)化知識點(diǎn)梳理總結(jié)的文章就介紹到這了,更多相關(guān)Android 內(nèi)存優(yōu)化 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android實(shí)現(xiàn)APP在線下載更新

    Android實(shí)現(xiàn)APP在線下載更新

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)APP在線下載更新的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Android實(shí)現(xiàn)未讀消息小紅點(diǎn)顯示實(shí)例

    Android實(shí)現(xiàn)未讀消息小紅點(diǎn)顯示實(shí)例

    大家好,本篇文章主要講的是Android實(shí)現(xiàn)未讀消息小紅點(diǎn)顯示實(shí)例,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • Android使用ViewPager實(shí)現(xiàn)無限滑動效果

    Android使用ViewPager實(shí)現(xiàn)無限滑動效果

    相信在大家開發(fā)Android的時候,我們常常用ViewPager來為自己的應(yīng)用創(chuàng)建廣告條幅,并且常常會遇到ViewPager無限滑動這樣的需求。下面來一起看看吧。
    2016-09-09
  • Android工具類整合教程

    Android工具類整合教程

    這篇文章主要介紹了Android工具類整合教程,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2020-09-09
  • Flutter 剪裁組件的使用

    Flutter 剪裁組件的使用

    今天我們主要聊聊 Flutter 中的幾個剪裁組件的使用,也是項(xiàng)目當(dāng)中經(jīng)常可以用到的,希望你可以有所收獲
    2021-06-06
  • android 仿微信demo——微信主界面實(shí)現(xiàn)

    android 仿微信demo——微信主界面實(shí)現(xiàn)

    本系列文章主要介紹了微信小程序-閱讀小程序?qū)嵗╠emo),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望能給你們提供幫助
    2021-06-06
  • Android?使用壓縮紋理的方案

    Android?使用壓縮紋理的方案

    這篇文章主要介紹了Android?使用壓縮紋理,本文介紹了什么是壓縮紋理,以及加載壓縮紋理的核心步驟,并在 Android OpenGLES 平臺上實(shí)現(xiàn)了壓縮紋理的顯示,需要的朋友可以參考下
    2022-09-09
  • Android Splash界面白屏、黑屏問題的解決方法

    Android Splash界面白屏、黑屏問題的解決方法

    這篇文章主要為大家詳細(xì)介紹了Android Splash界面白屏、黑屏問題的解決方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • 詳解如何在Android studio中更新sdk版本和build-tools版本

    詳解如何在Android studio中更新sdk版本和build-tools版本

    這篇文章主要介紹了如何在Android studio中更新sdk版本和build-tools版本,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Android PhotoView使用步驟實(shí)例詳解

    Android PhotoView使用步驟實(shí)例詳解

    這篇文章主要介紹了Android PhotoView使用步驟實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-06-06

最新評論

东乡| 方正县| 女性| 宜阳县| 富蕴县| 滨州市| 英吉沙县| 嘉善县| 绿春县| 南投市| 广东省| 大名县| 涞水县| 古田县| 鹿邑县| 黎平县| 锡林郭勒盟| 兴海县| 鄂尔多斯市| 留坝县| 鄂托克前旗| 中牟县| 汶川县| 长泰县| 虎林市| 陇西县| 施甸县| 察雅县| 龙陵县| 运城市| 天津市| 项城市| 五家渠市| 新兴县| 临海市| 崇明县| 正镶白旗| 庐江县| 凤阳县| 定安县| 平原县|