溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

發(fā)布時(shí)間:2021-06-08 13:53:45 來(lái)源:億速云 閱讀:202 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

1、Android內(nèi)存管理機(jī)制

1.1 Java內(nèi)存分配模型

先上一張JVM將內(nèi)存劃分區(qū)域的圖

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

程序計(jì)數(shù)器:存儲(chǔ)當(dāng)前線程執(zhí)行目標(biāo)方法執(zhí)行到第幾行。

棧內(nèi)存:Java棧中存放的是一個(gè)個(gè)棧幀,每個(gè)棧幀對(duì)應(yīng)一個(gè)被調(diào)用的方法。棧幀包括局部標(biāo)量表,
操作數(shù)棧。

本地方法棧:本地方法棧主要是為執(zhí)行本地方法服務(wù)的。而Java棧是為執(zhí)行Java方法服務(wù)的。
方法區(qū):該區(qū)域被線程共享。主要存儲(chǔ)每個(gè)類(lèi)的信息(類(lèi)名,方法信息,字段信息等)、靜態(tài)變量,常量,以及編譯器編譯后的代碼等。

堆:Java中的堆是被線程共享的,且JVM中只有一個(gè)堆內(nèi)存,主要存儲(chǔ)對(duì)象本身及數(shù)組

1.2 Dalvik和ART介紹

Dalvik:Dalvik是Google公司自己設(shè)計(jì)用于Android平臺(tái)的Java虛擬機(jī)。它可以支持已轉(zhuǎn)換為.dex格式的Java應(yīng)用程序的運(yùn)行,.dex格式是專(zhuān)門(mén)為Dalvik應(yīng)用設(shè)計(jì)的一種壓縮格式,適合內(nèi)存和處理器速度有限的系統(tǒng),Dalvik經(jīng)過(guò)優(yōu)化,允許在有限的內(nèi)存中同時(shí)運(yùn)行多個(gè)虛擬機(jī)實(shí)例,并且每一個(gè)Dalvik應(yīng)用做為獨(dú)立的Linux進(jìn)程執(zhí)行,獨(dú)立的進(jìn)程可以防止在虛擬機(jī)崩潰的時(shí)候所有程序都被關(guān)閉。

ART:ART表示Android Runtime,Dalvik是依靠一個(gè)just-In -Time編譯器去解釋字節(jié)碼,運(yùn)行時(shí)編譯后的應(yīng)用都需要通過(guò)一個(gè)解釋器在用戶的設(shè)備上運(yùn)行,這一機(jī)制并不是特別高效,但是能讓?xiě)?yīng)用更容易在不同的硬件和架構(gòu)上運(yùn)行。ART則是完全改變了這種做法,在安裝應(yīng)用的時(shí)候就預(yù)編譯字節(jié)碼到機(jī)器語(yǔ)言,這一機(jī)制叫預(yù)編譯。在移除解釋代碼這一過(guò)程,應(yīng)用程序執(zhí)行將更有效率,啟動(dòng)速度更快。

ART優(yōu)點(diǎn):

1.系統(tǒng)性能更高

2.應(yīng)用啟動(dòng)速度,運(yùn)行更快,體驗(yàn)更好,觸感反饋更加及時(shí)。

3.更長(zhǎng)的電池續(xù)航能力

4.支持更低的硬件

ART缺點(diǎn):

1.儲(chǔ)存空間占用更大。

2.應(yīng)用安裝時(shí)間更長(zhǎng)。

Dalvik與ART區(qū)別

1.Dalvik每次都要編譯在運(yùn)行,art只會(huì)安裝時(shí)啟動(dòng)編譯

2.art占用的空間比Dalvik要大,就是用空間換時(shí)間

3.art減少編譯,減少CPU使用頻率,使用明顯改善電池續(xù)航

4.art啟動(dòng),運(yùn)行更快,體驗(yàn)更好,觸感反饋更及時(shí)。

1.3 為什么要進(jìn)行內(nèi)存優(yōu)化

1.減少oom,提高應(yīng)用的穩(wěn)定性
2.減少卡頓,體驗(yàn)更好
3.減少內(nèi)存占用,應(yīng)用存活率更高
4.提前處理掉一些異常的隱患

2、Java內(nèi)存回收算法

2.1判斷Java中對(duì)象是否存活的算法

2.1.1 引用計(jì)數(shù)算法

堆內(nèi)存的每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)器,當(dāng)對(duì)象被引用的時(shí)候,計(jì)數(shù)器+1,當(dāng)引用失效時(shí)計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器的值為0時(shí),說(shuō)明該對(duì)象沒(méi)有被引用,就會(huì)被認(rèn)為是垃圾對(duì)象,系統(tǒng)將會(huì)將其回收內(nèi)存重新分配。

優(yōu)點(diǎn):引用計(jì)數(shù)器執(zhí)行簡(jiǎn)單,判定效率高。

缺點(diǎn):對(duì)于循環(huán)引用的對(duì)象難以判斷出來(lái),同時(shí)引用計(jì)數(shù)器增加了程序執(zhí)行的開(kāi)銷(xiāo),在jdk1.1后,就不在使用了。

2.1.1 根搜索法

GC Roots的對(duì)象做為起點(diǎn),然后向下搜索,搜索所走過(guò)的路徑稱(chēng)為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí),則該對(duì)象不可達(dá),也就是說(shuō)該對(duì)象為為垃圾對(duì)象,可以被回收。
在Java中,可以做為GC Roots的對(duì)象包括一下四種:

1.虛擬機(jī)棧中引用的對(duì)象

2.方法區(qū)中的類(lèi)靜態(tài)屬性引用的對(duì)象

3.方法區(qū)中常量引用的對(duì)象

4.本地方法棧中JNI的引用對(duì)象

2.2 JVM垃圾回收算法

2.2.1 標(biāo)記清除法

最基礎(chǔ)的垃圾收集算法,算法分為標(biāo)記和清除兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成之后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象。

缺點(diǎn):效率低,其次會(huì)產(chǎn)生大量的不連續(xù)的內(nèi)存碎片,導(dǎo)致提前觸發(fā)另一次垃圾收集動(dòng)作。

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

2.2.2 復(fù)制回收算法

復(fù)制回收算法是將可用內(nèi)存按容量分成大小相等的兩塊,每次只使用其中的一塊,當(dāng)這塊內(nèi)存使用完了,就將存活的對(duì)象復(fù)制到另一塊內(nèi)存上去,然后把使用過(guò)的內(nèi)存空間一次清理掉,這樣使得每都次都是對(duì)其中一塊內(nèi)存進(jìn)行回收,內(nèi)存分配時(shí)不用考慮內(nèi)存碎片等復(fù)雜情況。

缺點(diǎn):可使用內(nèi)存降為原來(lái)的一半。

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

2.2.3 標(biāo)記整理法

標(biāo)記-整理算法在標(biāo)記-清除算法的基礎(chǔ)上做了改進(jìn),標(biāo)記階段將可回收的對(duì)象標(biāo)記出來(lái),標(biāo)記完成后不是直接對(duì)可回收的對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),在移動(dòng)的過(guò)程中清理掉可回收的對(duì)象。

優(yōu)點(diǎn):相比于標(biāo)記清除法來(lái)說(shuō),標(biāo)記整理法不會(huì)大量產(chǎn)生不連續(xù)內(nèi)存碎片問(wèn)題。

缺點(diǎn):如果是在對(duì)象存活率較高的情況下會(huì)執(zhí)行較多的復(fù)制操作,效率將會(huì)降低很多,而在存活率較低的情況下,效率會(huì)大大提高。

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

2.2.4 分代收集回收算法

當(dāng)前商業(yè)虛擬機(jī)都是采用的是分代收集算法,根據(jù)對(duì)象存活的周期不同將內(nèi)存劃分為幾塊,一般是將java堆分為年輕代,老年代和永久代。然后根據(jù)各個(gè)年代的特點(diǎn)來(lái)采取不同收集算法,年輕代存活率較低,采用復(fù)制回收算法,老年代對(duì)象存活率較高,采用標(biāo)記清除法或者是標(biāo)記整理法來(lái)進(jìn)行回收。

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

3、內(nèi)存問(wèn)題表現(xiàn)形式

3.1 內(nèi)存抖動(dòng)

內(nèi)存波動(dòng)圖呈鋸齒狀,gc頻繁導(dǎo)致卡頓。

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

3.2 內(nèi)存泄漏

內(nèi)存泄露簡(jiǎn)單來(lái)說(shuō)就是系統(tǒng)分配出去的內(nèi)存由于某種原因?qū)е聸](méi)法釋放,內(nèi)存會(huì)越來(lái)越小,最終導(dǎo)致oom。

3.3 內(nèi)存溢出

即OOM,OOM時(shí)會(huì)導(dǎo)致程序異常。Android設(shè)備出廠以后,java虛擬機(jī)對(duì)單個(gè)應(yīng)用的最大內(nèi)存分配就確定下來(lái)了,超出這個(gè)值就會(huì)OOM。

4、內(nèi)存優(yōu)化常用工具

4.1 Memory Profiler

Memory Profiler是Android studio自帶的工具,實(shí)時(shí)圖表形式展示應(yīng)用內(nèi)存使用的情況,可以用來(lái)識(shí)別內(nèi)存泄露,抖動(dòng)等
注意:如果在控制臺(tái)中沒(méi)有找到Profiler,可View -----> Tool Windows ---> Profiler 進(jìn)行打開(kāi)

優(yōu)點(diǎn):方便直觀,便于線下使用

4.2 Memory Analyzer(MAT)

1、強(qiáng)大的java heap分析工具,查找內(nèi)存泄露及內(nèi)存占用

2、生成整體報(bào)告,便于分析問(wèn)題

3、可以在線下深入使用

MAT使用:

MAT下載地址:https://www.eclipse.org/mat/downloads.php

獲取hprof文件

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

導(dǎo)出來(lái)的Dump是沒(méi)法直接使用mat打開(kāi)的,Android SDK自帶了一個(gè)轉(zhuǎn)換工具在SDK的platform-tools下,其中轉(zhuǎn)換語(yǔ)句為:

cd D:\aa\sdk\platform-tools

hprof-conv aaa.hprof  bbb.hprof

注:aaa.hprof表示從profiler中導(dǎo)出來(lái)的dump文件,bbb.hprof 表示轉(zhuǎn)化出來(lái)的dump文件

使用mat打開(kāi)轉(zhuǎn)化出來(lái)的dump

MAT視圖

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

在MAT窗口上,OverView是一個(gè)總體概覽,顯示總體的內(nèi)存消耗情況和疑似問(wèn)題。

1、Histogram:列出內(nèi)存中的所有實(shí)例對(duì)象和個(gè)數(shù)以及大小,在頂部regex區(qū)域支撐正則表達(dá)式查找

2、Dominator Tree:列出最大的對(duì)象及其依賴存活的Object,相比于Histogram,能更方便的看出引用關(guān)系。

3、Top Consumers:通過(guò)圖像列出最大的Object

4、Leak Suspects:通過(guò)MAT自動(dòng)分析內(nèi)存泄露的原因和泄露的一份總體報(bào)告

其中分析內(nèi)存情況,我們基本用到的就是Histogram和Dominator Tree

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

Class Name:類(lèi)名。

Objects:對(duì)象實(shí)例個(gè)數(shù)。

Shallow Heap:對(duì)象自身占用內(nèi)存大小,不包括它引用的對(duì)象

Retained Heap:是當(dāng)前對(duì)象大小和直接或者間接引用到的對(duì)象大小總和,包括遞歸釋放的。

查找內(nèi)存泄露方式

步驟一:在Regex通過(guò)包名進(jìn)行匹配,當(dāng)然也可以通過(guò)其他方式進(jìn)行匹配

步驟二:右鍵選中懷疑對(duì)象,List objects --> with incoming references

注   with outgoing references 他引用了那些對(duì)象

with incoming references 那些對(duì)象引用了他

步驟三:選擇當(dāng)前的一個(gè) Path to GC Roots/Merge to GC Roots 的 exclude All 弱軟虛引用。

Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析

圖標(biāo)的左下角出現(xiàn)這個(gè),則表示出現(xiàn)了內(nèi)存泄露。然后回調(diào)代碼中分析即可。

4.3 LeakCanary

使用

implementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'

application中

public class App extends Application {

    private RefWatcher mRefWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
     mRefWatcher = LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        App application = (App) context.getApplicationContext();
        return application.mRefWatcher;
    }
 }

在activity或者fragment中的onDestory()方法調(diào)用

RefWatcher refWatcher = App.getRefWatcher(getActivity());
refWatcher.watch(this);

原理

主要是通過(guò)WeakReference + ReferenceQueue來(lái)判斷對(duì)象是否被系統(tǒng)GC回收,WeakReference創(chuàng)建時(shí),傳入一個(gè)ReferenceQueue對(duì)象,當(dāng)WeakReference引用的對(duì)象生命周期結(jié)束后,會(huì)被添加到ReferenceQueue中,當(dāng)GC過(guò)后,對(duì)象一直沒(méi)有被添加進(jìn)入到ReferenceQueue,可能就會(huì)存在內(nèi)存泄露,再次觸發(fā)GC,二次確認(rèn)。

5、常見(jiàn)的內(nèi)存泄露

1、資源性對(duì)象未關(guān)閉

對(duì)于資源性對(duì)象不再使用時(shí),應(yīng)該立即調(diào)用它的close()函數(shù),將其關(guān)閉,然后再置為null。例如Bitmap等資源未關(guān)閉會(huì)造成內(nèi)存泄漏,此時(shí)我們應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉。

2、注冊(cè)對(duì)象未注銷(xiāo)

例如BraodcastReceiver、EventBus未注銷(xiāo)造成的內(nèi)存泄漏,我們應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)注銷(xiāo)。

3、類(lèi)的靜態(tài)變量持有大數(shù)據(jù)對(duì)象

盡量避免使用靜態(tài)變量存儲(chǔ)數(shù)據(jù),特別是大數(shù)據(jù)對(duì)象,建議使用數(shù)據(jù)庫(kù)存儲(chǔ)。

4、單例造成的內(nèi)存泄漏

優(yōu)先使用Application的Context,如需使用Activity的Context,可以在傳入Context時(shí)使用弱引用進(jìn)行封裝,然后,在使用到的地方從弱引用中獲取Context,如果獲取不到,則直接return即可。

5、非靜態(tài)內(nèi)部類(lèi)的靜態(tài)實(shí)例

該實(shí)例的生命周期和應(yīng)用一樣長(zhǎng),這就導(dǎo)致該靜態(tài)實(shí)例一直持有該Activity的引用,Activity的內(nèi)存資源不能正?;厥?。此時(shí),我們可以將該內(nèi)部類(lèi)設(shè)為靜態(tài)內(nèi)部類(lèi)或?qū)⒃搩?nèi)部類(lèi)抽取出來(lái)封裝成一個(gè)單例,如果需要使用Context,盡量使用Application Context,如果需要使用Activity Context,就記得用完后置空讓GC可以回收,否則還是會(huì)內(nèi)存泄漏。

6、Handler臨時(shí)性內(nèi)存泄漏

Message發(fā)出之后存儲(chǔ)在MessageQueue中,在Message中存在一個(gè)target,它是Handler的一個(gè)引用,Message在Queue中存在的時(shí)間過(guò)長(zhǎng),就會(huì)導(dǎo)致Handler無(wú)法被回收。如果Handler是非靜態(tài)的,則會(huì)導(dǎo)致Activity或者Service不會(huì)被回收。并且消息隊(duì)列是在一個(gè)Looper線程中不斷地輪詢處理消息,當(dāng)這個(gè)Activity退出時(shí),消息隊(duì)列中還有未處理的消息或者正在處理的消息,并且消息隊(duì)列中的Message持有Handler實(shí)例的引用,Handler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無(wú)法及時(shí)回收,引發(fā)內(nèi)存泄漏。解決方案如下所示:

  • 1、使用一個(gè)靜態(tài)Handler內(nèi)部類(lèi),然后對(duì)Handler持有的對(duì)象(一般是Activity)使用弱引用,這樣在回收時(shí),也可以回收Handler持有的對(duì)象。

  • 2、在Activity的Destroy或者Stop時(shí),應(yīng)該移除消息隊(duì)列中的消息,避免Looper線程的消息隊(duì)列中有待處理的消息需要處理。需要注意的是,AsyncTask內(nèi)部也是Handler機(jī)制,同樣存在內(nèi)存泄漏風(fēng)險(xiǎn),但其一般是臨時(shí)性的。對(duì)于類(lèi)似AsyncTask或是線程造成的內(nèi)存泄漏,我們也可以將AsyncTask和Runnable類(lèi)獨(dú)立出來(lái)或者使用靜態(tài)內(nèi)部類(lèi)。

7、容器中的對(duì)象沒(méi)清理造成的內(nèi)存泄漏

在退出程序之前,將集合里的東西clear,然后置為null,再退出程序

8、WebView

WebView都存在內(nèi)存泄漏的問(wèn)題,在應(yīng)用中只要使用一次WebView,內(nèi)存就不會(huì)被釋放掉。我們可以為WebView開(kāi)啟一個(gè)獨(dú)立的進(jìn)程,使用AIDL與應(yīng)用的主進(jìn)程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷(xiāo)毀,達(dá)到正常釋放內(nèi)存的目的。

9、使用ListView時(shí)造成的內(nèi)存泄漏

在構(gòu)造Adapter時(shí),使用緩存的convertView。

6、優(yōu)化內(nèi)存空間的方式

6.1、java對(duì)象的引用

強(qiáng)引用:我們平時(shí)開(kāi)發(fā)寫(xiě)的代碼,基本百分之九十九的都是強(qiáng)引用。

軟引用:如果一個(gè)對(duì)象具有軟引用,那么當(dāng)內(nèi)存不足時(shí),就會(huì)回收它。

弱引用:GC時(shí),只要發(fā)現(xiàn)有弱引用,那么就會(huì)回收它,當(dāng)然,有可能存在GC多次才發(fā)現(xiàn)

虛引用:虛引用必須要和引用隊(duì)列關(guān)聯(lián)起來(lái)使用。任何時(shí)候都有可能被垃圾回收器回收。一般可以用來(lái)判斷GC的頻率,GC頻率過(guò)高,那么說(shuō)明內(nèi)存出了問(wèn)題。同時(shí)也可以監(jiān)聽(tīng)某個(gè)重要的對(duì)象是否被回收。

所以,在平時(shí)我們編寫(xiě)代碼的時(shí)候,適當(dāng)?shù)氖褂密浺茫跻?,?duì)我們的內(nèi)存優(yōu)化也能起到重要的作用。

6.2、減少不必要的內(nèi)存開(kāi)銷(xiāo)

1、AutoBoxing

自動(dòng)裝箱的核心是吧基礎(chǔ)數(shù)據(jù)類(lèi)型轉(zhuǎn)換成對(duì)應(yīng)的包裝類(lèi),比如int 類(lèi)型只是占用4字節(jié),但是Integer對(duì)象占用16字節(jié)。

2、內(nèi)存復(fù)用

資源復(fù)用:通用的字符串,顏色定義,簡(jiǎn)單頁(yè)面布局的復(fù)用

視圖復(fù)用:進(jìn)行布局復(fù)用

3、使用優(yōu)化過(guò)的數(shù)據(jù)類(lèi)型

如 SparseArray、SparseBooleanArray、LongSparseArray,使用這些API可以讓我們的程序更加高效。HashMap 工具類(lèi)會(huì)相對(duì)比較 低效,因?yàn)樗?需要為每一個(gè)鍵值對(duì)都提供一個(gè)對(duì)象入口,而 SparseArray 就 避免 掉了 基本數(shù)據(jù)類(lèi)型轉(zhuǎn)換成對(duì)象數(shù)據(jù)類(lèi)型的時(shí)間。

4、項(xiàng)目中少用枚舉

枚舉占用內(nèi)存是常量三倍。

5、在應(yīng)用可以內(nèi)存過(guò)低時(shí)主動(dòng)釋放內(nèi)存

在application中的 onTrimMemory/onLowMemory,內(nèi)存緊張時(shí)會(huì)回調(diào)該方法,我們可以在這個(gè)方法中釋放掉圖片緩存,靜態(tài)緩存來(lái)避免被kill。

6、避免創(chuàng)建一些不必要的對(duì)象

如在字符串拼接時(shí)不要用“+”來(lái)進(jìn)行拼接,而是使用StringBuffer,StringBuilder來(lái)替代。因?yàn)镾tring 內(nèi)部是被final修飾的,不可繼承,使用+進(jìn)行拼接是會(huì)產(chǎn)生一個(gè)新的對(duì)象,而占用內(nèi)存。

7、盡量不要在一些循環(huán)的地方創(chuàng)建對(duì)象。

如自定義的時(shí)候在onDraw()方法。

7、優(yōu)雅的檢測(cè)大圖

項(xiàng)目中會(huì)經(jīng)常遇到這樣的情況,我們的布局中,控件的寬高可能只是50 * 50 但是從服務(wù)器給過(guò)來(lái)的圖片或者是UI給過(guò)來(lái)的圖片往往會(huì)大很多,而如果圖片在資源文件下還好,可以直接查看寬高,但是如果從服務(wù)器上獲取到的呢,這是我們經(jīng)常會(huì)忽略的。而圖片過(guò)大,占用的內(nèi)存就更多,這是沒(méi)有必要的。那么我們?cè)趺礄z測(cè)出服務(wù)器給過(guò)來(lái)的圖片過(guò)大的呢?

7.1、繼承ImageView 重新實(shí)現(xiàn)onDraw()

這種方法我們可以重新測(cè)量圖片的寬高,超過(guò)一定的范圍,我們就可以輸出警告。但是這種方法對(duì)代碼侵入性很強(qiáng)。如果是有新同學(xué)加入,容易造成代碼混亂。

7.2、ARTHook

Hook的意思是鉤子,也就是在消息過(guò)去之前可以把消息勾住,不讓其傳遞,能夠針對(duì)不同的消息或者api在執(zhí)行之前,先執(zhí)行我們自己的操作。

這里推薦使用Epic 框架:https://github.com/tiann/epic

添加依賴

implementation 'me.weishu:epic:0.3.6'

創(chuàng)建一個(gè)ImageHook類(lèi)

package com.optimize.performance.memory;

import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;

import com.optimize.performance.utils.LogUtils;
import com.taobao.android.dexposed.XC_MethodHook;

public class ImageHook extends XC_MethodHook {

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        // 實(shí)現(xiàn)我們的邏輯
        ImageView imageView = (ImageView) param.thisObject;
        checkBitmap(imageView,((ImageView) param.thisObject).getDrawable());
    }

    private static void checkBitmap(Object thiz, Drawable drawable) {
        if (drawable instanceof BitmapDrawable && thiz instanceof View) {
            final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            if (bitmap != null) {
                final View view = (View) thiz;
                int width = view.getWidth();
                int height = view.getHeight();
                if (width > 0 && height > 0) {
                    // 圖標(biāo)寬高都大于view帶下的2倍以上,則警告
                    if (bitmap.getWidth() >= (width << 1)
                            && bitmap.getHeight() >= (height << 1)) {
                        warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large"));
                    }
                } else {
                    final Throwable stackTrace = new RuntimeException();
                    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                        @Override
                        public boolean onPreDraw() {
                            int w = view.getWidth();
                            int h = view.getHeight();
                            if (w > 0 && h > 0) {
                                if (bitmap.getWidth() >= (w << 1)
                                        && bitmap.getHeight() >= (h << 1)) {
                                    warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace);
                                }
                                view.getViewTreeObserver().removeOnPreDrawListener(this);
                            }
                            return true;
                        }
                    });
                }
            }
        }
    }
    
    
    private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
        String warnInfo = new StringBuilder("Bitmap size too large: ")
                .append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')')
                .append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')')
                .append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n')
                .toString();

        LogUtils.i(warnInfo);
    }

}

在application中

DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook());
            }
        });

這樣在開(kāi)發(fā)者調(diào)用setImageBitmap 來(lái)設(shè)置圖片的時(shí)候,都會(huì)進(jìn)行對(duì)圖片的寬高進(jìn)行比如,如果超出一定的范圍則進(jìn)行提示。

以上是“Android性能優(yōu)化之內(nèi)存優(yōu)化的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI