Jianwoo中的性能優化 — 如何優化到打開170個界面不卡頓

前言

簡物作為一個電商app,內容最多的就是圖片,而在應用性能優化中,圖片又是一個非常關鍵的點,因為性能優化涉及到兩個方面:1、繪制 2、內存。而圖片所代表的內存,則是電商app中比較大頭的一個優化模塊,而在別的一些領域占內存較多的可能不僅僅是圖片,還有音頻、視頻數據等等,簡物中所涉及到的內存優化,主要是針對圖片,當然不僅僅是說用什么圖片加載框架,如果僅僅是這樣,那這篇文章就有點過于口水了

性能優化是一個非常有的聊的話題內容,不管是過度繪制還是內存泄漏還是內存溢出都有足夠你學習好幾天甚至一個月的內容,深入到底層去了解更是有很多東西需要學習,我作為寫這篇文章的作者,也有非常非常多的東西需要學,如果這篇文章有什么地方講述的不對,還請指正,本文所針對的優化,都有實際結果作為支撐,如果你能理解我所說的那應該會對你項目實際應用有一定的幫助

簡物中的優化過程&結果

第一階段1

  • 未做優化能深度打開30-40個界面,由于詳情頁的圖片列表占內存比較大,內存會慢慢從25M左右飆升到270M直到變卡頓出現ANR/OOM
未處理內存前的內存折線飆升

其實對于首頁的不同Fragment已經是懶加載,從Android Monitor的內存占用來看,啟動頁只占了12M(啟動圖所占的內存)

啟動頁

在啟動頁結束之后,進入首頁只加載第一個Fragment,內存占用是26M
首頁第一個Fragment加載

也就是我們在不加載其它Fragment界面的時候,首頁的內存占用其實是不高的(這種列表頁只有不同模塊的加載,本身只可能存在過度繪制問題,不會有內存占用過高的問題)

對于打開的界面是什么部分占用內存過高,我們可以使用工具來監測一下


檢測一段操作的內存使用

通過幾次測試我們發現,頁面中大部分占用內存的地方都是來自于com.facebook.*的包,因為簡物使用的是Fresco,所以圖片加載正是用的它的SimpleDrawable,通過檢測我們也能看出,這個操作中最占內存的就是圖片,那我們必須從圖片下手

第二階段:針對第一階段所出現的內存飆升問題 ,做了如下處理

  • 在云存儲后臺設置圖片壓縮比例,在不影響圖片視覺感官的情況下降低圖片質量
    處理結果:處理了圖片質量后大概將內存占用量降低到了70%,這也就是意味著,用戶可以打開更多界面,但僅僅是可以打開更多界面,在打開到45-60個界面左右的時候,APP基本就無響應了,也就是沒有根本解決問題

第三階段 云存儲平臺設置圖片壓縮后并未完全解決問題,繼續往下處理

  • 對占內存指數較高的商品詳情界面實行不可見時釋放內存,這是實實在在能解決內存飆升的解決方案
    分析:我們在使用app的時候其實可以發現,在沒有內存泄漏的前提下,我們打開兩三個商品詳情,然后依次退出,再從首頁別的列表進入商品詳情,再退出,其實是不會造成app崩盤閃退的,為什么,因為這種情況下,你瀏覽完商品詳情后退出,系統會自動回收資源,因為這個時候相當于你的資源以及未使用,Activity釋放后,那些未被引用的對象也會相繼釋放,也就是,如果我們能釋放商品詳情的資源,那我們就能控制內存的飆升不降了
    處理結果:在界面不可見的時候釋放內存,效果非常顯著,從Android Monitor可以看到內存的曲線波動,會非常有節奏的升降(注意:以下的內存跳閘下降并非Activity退出
深層次打開界面的內存波動曲線圖

這里暫時只寫優化結果,具體優化細節以及要注意的問題在后面會有詳細的過程

第四階段

  • 其實現在打開深層次界面內存飆升問題已經解決了,但是在應用打開app逐漸變多的時候,依然會出現Activity專場以及列表滑動慢慢變的不流暢,直到出現ANR,內存不是在Android Monitor已經很有節奏的升降了嗎,但是卻依然ANR,通過Android Monitor可以看到,內存這一欄目確實沒有飆升,但是在CPU這個欄目卻有非常大的波動,這個時候很明顯已經不是內存溢出的問題了,而是過度繪制問題
    處理結果:在處理完繪制問題后,性能得到了很明顯的提升,可以連續打開>100個界面無卡頓

以上都沒有寫詳細處理細節,只是簡單的講述了一下流程,下面開始講解內存泄漏、內存溢出、以及過度繪制的問題,以及如何在Activity不可見的時候釋放內存

注意

性能調優是一件非常不簡單的活,尤其是在不知道什么地方出現問題導致響應過慢或者無響應或者內存溢出的時候,這個時候可能需要查看log日志或者用到Traceview、Oprofile、MAT等工具來進行檢查,這些工具能顯示出哪個函數消耗CPU時間最多,哪個對象占用內存最高,Android Monitor僅僅是一個性能指數顯示的一個工具,只是調優環節的一個顯示器而已
本文所講的內存調優并非針對毛病問題的排查,所以不會涉及到這些工具的使用,更多的是如何避免內存泄漏、內存溢出、過度繪制等問題,也是針對我處理過問題所做的一個筆記,在此分享出來

實踐

在優化之前,要先弄清楚幾個問題,1:什么是內存泄漏(memory leak) 2:什么是內存溢出(out of memory) 3:什么是過度繪制(overdraw)、4、什么是ANR(application not responding)?,我們一邊理解這幾個問題一邊聊遇到的問題是怎么解決的

  • 1、什么是內存泄漏
    我們使用的對象都有著他們自己的生命周期,在那些對象的生命周期完成各自的使命后我們希望它能被系統回收,但這個時候這個對象因為依然被那些生命周期更長的對象持有引用而導致系統不能回收它,導致內存一直被占用,這就是內存泄漏
    為什么會出現不能被回收的現象,不能強制回收嗎?這就跟Java的垃圾回收機制有關了,Java的垃圾回收機制中一種算法是依據對象的引用計數器來判斷是否回收的,也就是說,如果一個對象始終被其它對象持有引用,那這個對象將無法被垃圾回收識別為垃圾,認為這個對象還在使用,那這個時候這個對象就一直占著茅坑不拉屎,就導致了內存泄漏,在我們Android開發中,什么情況下容易存在這種對象被無故引用的情況,上面其實已經說,凡是生命周期短的被生命周期長的對象持有引用,都有可能存在內存泄漏,我們來舉個例子

案例

public class MemoryLeakActivity extends AppCompatActivity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.sendMessageDelayed(Message.obtain(), 8000);
    }

    @Override
    protected void onStart() {
        super.onStart();
        finish();
    }

    Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            //@Todo
            return true;
        }
    });
}

為什么這段代碼會內存泄漏?
首先Handler在這個Activity內部不是靜態內部類,導致Handler內部持有外部Activity的一個引用,什么叫做持有一個引用?怎么能看出是不是持有引用?比如你在Activity內部定義一個成員變量int a = 0;你可以在這個Handler內部可以拿a的引用去賦值,這就是持有引用的一個表現,那為什么在這里持有Activity的引用會導致內存泄漏呢?因為在這里,Handler的生命周期和Activity不一致
我們知道Activity是運行與Android的主線程上,而主線程在創建的時候,內部會自動創建一個Looper對象,Looper創建的時候內部會實例化一個MessageQueue(消息池),然后Looper會不斷的循環消息池,看里面有沒有消息,有消息的話就把消息拿出來處理,這就是消息隊列機制,那這跟Handler有啥關系呢?Handler內部有一個Looper對象,在你初始化Handler的時候如果沒有在構造函數中傳入一個Looper,那它會試圖從當前線程取出一個Looper引用進行賦值,也就是調用Looper.myLooper()來給當前Looper對象賦值引用,那如果調用當前線程沒有拿到Looper那就會拋出運行時異常了,所以在非主線程中使用Handler時,如果當前線程沒有Looper對象,那是無法初始化Handler的,Handler離開Looper是無法使用的,那這其實也就是說明,我們的Handler內部是持有主線程的Looper引用,所以導致Handler的生命周期跟隨整個主線程一致,從而導致和Activity的生命周期不一致,于是在我們finish Activity的時候,會導致Activity的資源無法使用,因為“它還在被Handler引用著”
那針對上面那個大bug,我們要怎么改呢?

  • 1、使用靜態內部類或外部類避開持有外部類的應用
  • 2、如果需要傳遞使用Activity的引用,那使用弱引用
  • 3、在Activity結束時的生命周期函數釋放Handler中的消息隊列和回調
public class MemoryLeakActivity extends AppCompatActivity {

    MHandler mHandler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new MHandler(this);
        mHandler.sendMessageDelayed(Message.obtain(), 8000);
    }

    @Override
    protected void onStart() {
        super.onStart();
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

    static class MHandler extends Handler{

        WeakReference<MemoryLeakActivity> mActivity;

        public MHandler(MemoryLeakActivity activity) {
            this.mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if(mActivity.get() != null){
                //@TODO
            }
        }
    }
}

以上是Android中內存泄漏的一個栗子,凡是只要符合“生命周期短的對象被生命周期長的對象持有引用”的條件,均有可能發生內存泄漏,比如Thread、AsyncTask、單例模式,當然資源未關閉如File、Stream、Bitmap、Cursor等也會造成內存泄漏

  • 2、什么是內存溢出?
    內存溢出是指程序在運行期間所占用的內存超出了虛擬機所分配的最大內存,這個時候就會拋出out of memory,也就是內存泄漏和內存溢出并非直導關系,內存泄漏雖然會導致對浪費的內存失去控制,但是如果內存沒有超過程序所能支配的上限那也是不會內存溢出的。怎樣的情況下會內存溢出呢,比如你讀取一個本地大文件到內存,只要你的文件足夠大,你又沒有做響應的處理僅僅是讀進內存,那終究會有一個點讓你的程序內存溢出;又比如我們在使用app的時候,不停的打開Activity,每個Activity都有很多圖片,那這個時候如果我們沒有做響應的優化處理,那終究也會有一個點你打開下一個Activity內存溢出;再比如我們做圖片處理,如果讀取到內存中的圖片沒有做裁剪壓縮,那也有可能在一些機型上因為內存分配不足而導致內存溢出等等等等

  • 3、什么是過度繪制
    過度繪制是指一個像素點在一幀的刷新時間內被繪制了多次,那這會導致什么問題呢,Android系統在正常的情況下回每隔16ms向系統發送刷新界面的信號以觸發系統對UI界面的渲染,在1秒(1000ms)的時間內每隔16ms進行一次刷新,如果刷新比較順暢那就能達到1000/16 ≈ 60幀的刷新頻率,那對于我們肉眼來說是非常流暢的,但是如果因為界面嵌套過深,或者布局有著重疊的部分,并且重疊的部分均要繪制背景色,那這個時候就會出現“過度繪制”,過度繪制會導致CPU/GPU的性能損耗,導致16ms內無法完成計算、繪制、渲染的操作,這個時候就會覺得應用不流暢
    既然過度繪制可能會導致嚴重的問題,那我們如何來查看過度繪制情況呢?Android系統自帶的工具就能顯示我們的app的過度繪制情況,具體路徑在設置 ->
    開發者選項 -> 調試GPU過度繪制 -> 顯示過度繪制區域(不同手機設置可能不一樣)

調試GPU過度繪制

那這個時候我們就可以來調試過度繪制問題了,打開APP你會發現大概有四種顏色的區域色塊(因為截圖顯示的不同色塊不好做標注,所以從網上找了一張圖),從1x — 4x代表了過度繪制程度由低到高,大片紫色或者藍色說明你的布局優化還是很不錯的

過度繪制程度

簡物在沒有做過度繪制調試的時候也存在很明顯的過度繪制問題,在先后調試后有非常明顯的效果提升,比如 我的個人中心界面


overdraw

從左到右過度繪制的情況有很明顯的下降,那在這個過程中我做了什么呢?這里面顏色重疊比較深的區域均是因為設置了重復的背景色,或者底部看不到的Layout也設置了背景色,導致過度繪制,所以我只是依次檢測布局層次中的背景色設置,把不需要或者看不到背景色的設置背景色為以下透明色,就可以降低很多紅色區域

android:background="@color/translucent"

在這里有一個在設置主題上的一個巨坑,如果你的主題里面設置了這個參數

<item name="android:windowIsTranslucent">true</item>

這將會導致你的Activity在不可見的時候也加入繪制,導致頁面打開層次過多的時候非常卡頓(即使內存優化做到了位,內存不飆升,也會因為過度繪制導致CPU/GPU使用率過高而出現不流暢直至ANR)

過度繪制除了出現在以上問題中,還有什么地方會導致過度繪制?
在任何時候只要View的繪制內容出現了變化,都會導致屏幕重新渲染,所以我們應該盡量避免容器或者View的重新繪制、計算,這也是為什么LineaLayout會比RelativeLayout性能要高,因為RelativeLayout的布局計算會因為內部的一個View的大小改變而影響到整個容器的寬高變化,從而導致要重新計算、繪制,同樣簡單的嵌套一個View,在LineaLayout沒有設置weight的情況下,RelativeLayout會調用兩次onMeasure,LineaLayout只調用一次,在同等嵌套層次的情況下盡量使用LineaLayout,在根布局也最好使用LineaLayout或者FrameLayout,當然我們應該盡量減少布局層次的潛逃,如果因為使用LineaLayout會導致層次加深,那可以考慮使用RelativeLayout來減少嵌套層次
另外有一個小技巧可以在這里提一下,在Activity生命周期的幾個函數中,不要在onCreate方法做初始化UI以及初始化數據的操作,onCreate只做屏幕參數以及setContentView的操作,在onStart方法里面去做UI綁定以及UI默認數據初始化,以及界面數據初始化(不過要考慮onStart在生命周期中調用的場景)

  • 什么是ANR?
    ANR(Application not responding)其字面意思就是應用程序無響應,ANR一般分為三類
    1:用戶輸入行為無響應,比如觸摸按鍵,控件監聽事件(5秒內未響應Android會提示ANR窗口)
    2:BroadcastReceiver的onReciver方法未響應(10秒時間未響應會提示ANR)
    3:Service處理超時未響應(20秒內)

以上三類情況,1是比較常見的,2、3可能相對較少
那什么情況下會導致以上情況ANR呢?可能是
1、主線程執行了耗時操作
2、BroadcastReceiver中onReceive做耗時操作
3、主線程執行IO
4、主線程使用了Thread.sleep Thead.wait方法,(應該使用Handler來處理操作結果,而不是使用時間等待來阻塞線程)
5、Activity生命周期中onCreate、onResume執行耗時操作
如何查看ANR出現的原因呢?在手機的目錄中/data/anr/traces.txt可以查看并且分析ANR出現的原因(IOwait、block、memoryleak)

以上四個問題已經大概的了解了,那我們可以開始進入簡物中優化性能的正題,如何從打開30-40個界面優化到打開測試到170個界面無卡頓

這里忘了提一個Activity啟動模式的問題,對于電商App來說,詳情頁是打開量比較大的界面,有些人可能會建議使用SingleTop、SingleTask模式來作為詳情頁Activity的啟動模式,但是SingleTop作為棧頂復用僅僅是在棧頂的時候才在當前Activity加載數據,而對于SingleTask會將詳情頁以上的Activity實例全部銷毀,很明顯,這兩種模式都無法完美的解決我們的問題,反而會帶來一系列使用體驗上的bug,那我們為了完美的解決問題來做一下流程分析

  • 1、首先,必須要保證程序沒有內存泄漏,如果程序存在內存泄漏,那及時你手動去釋放資源,也無法將泄漏的對象釋放資源,因為其對象依然被引用,Java無法將其視為垃圾內存,上面已經提到了如何避免內存泄漏,我們在開發的過程中就應該避免寫有內存泄漏風險的代碼,如果通過Android Monitor觀察內存曲線以及在退出Activity的時候點擊GC按鈕內存無法下降,那就可能存在內存泄漏了,這可以使用LeakCanary工具檢查Activity內存泄漏,這是一款非常實用的工具,可以自行百度查找相關使用方法,這篇文章篇幅過長,不作詳細介紹
    2、在前面我們已經說了,電商App占用資源最多的是詳情頁和商品列表頁,如果我們的程序沒有內存溢出,我們可以發現其實在Activity全部退出的時候內存是能降下來的,不會因為打開的頁面堆積而退出也無法釋放內存,那這其實是我們的一個突破點
    既然Activity退出可以下降內存,那也就意味著Activity資源釋放后,那些被釋放資源的內存是可以被標記為垃圾內存的,那我們能不能在Activity沒退出的時候就把資源釋放呢?答案是肯定可以的,如果我們在Activity不可見,或者檢測onTrimMemory的回調級別來釋放Activity的資源,那我們不就可以實現即使Activity仍然在任務棧,也可以釋放資源而不導致頁面疊加過多而導致OOM的需求嗎
    那主流的APP有哪些采用了這種形式:寫這篇文章比較匆忙,也沒有去做過多的測試,比較明顯的一個電商APP 禮物說 是采用釋放不可見資源來達到內存優化的目的(不過并非界面不可見立馬釋放,應該是監聽了onTrimMemory),當界面再次加載時重載界面數據
    這里我貼一張簡物實現不可見界面資源釋放,重新加載是怎樣的效果(圖片可以明顯看到界面重載,實際應用中可以使用一個loading效果來掩飾這個加載過程)
內存優化后的簡物

注意看,我在重載后還把界面位移還原了,實際情況可以有一個loading動畫

那我們就朝著這個方向,去做分析,如何實現這個目標(這里針對簡物的優化實踐,不對其它情況(如音視頻的情況)做闡述)
1、對于電商App而言,毫無疑問圖片是占用內存的主要元兇,我們這里分為兩種情況 1、詳情頁的圖片和主圖 2、商品列表頁的圖片(Recyclerview的圖片加載和釋放),注意:對于圖片至少要有一級本地緩存,釋放資源是釋放內存中的資源,本地資源不作銷毀,因為再次加載是從本地加載圖片,本地加載速度僅次于內存
2、對于占用的圖片資源,除了釋放對應的Bitmap之外,被其使用所顯示的View也要移除,否則資源被引用,Java是不會將其標記為垃圾內存的
3、因為我們再次更新界面時需要使用到數據源,所以要考慮對網絡請求的數據做備份 1、內存不緊張可以存成員變量 2、內存緊張序列化到本地文件/存儲數據庫,再次可見時取備用數據更新界面
分析完之后其實發現,要做內存釋放其實不難,主要是需要理解原理和操作對象

首先是詳情頁請求數據后需要對數據進行備份,那我在這詳情頁的數據做了深克隆備份,在網絡回調的地方這樣處理


    SaleDetailBean.SaleDetail mSaleDetail;
    SaleDetailBean.SaleDetail mSaleDetailCopy;

    @Override
    public void getSaleDetailSucess(SaleDetailBean.SaleDetail saleDetail) {
        if(saleDetail == null){
            return;
        }

        /**
         * 初次請求數據不做主圖二次設置
         */
        if(mSaleDetail != null){
            mImage.setImageURI(Uri.parse(getTitleImage()));
        }
        this.mSaleDetail = saleDetail;
        this.mSaleDetailCopy = saleDetail.clone();
        updateViews();
    }

備份數據后可以開始寫資源釋放的方法,剛剛我們說了,對于電商類App來說,圖片是占用最多的,那我們應該從返回網絡數據的圖片地址去釋放對應的內存資源,因為每個人的圖片加載以及緩存釋放方式都不一樣,這里以Fresco為例

    public void clear(){
       if(mSaleDetailCopy == null){
            return;
       }

        /**
         * 對圖片進行清空,非圖片資源進行默認數據初始化
         */
        mTitle.setText("————");
        mDescribe.setText("——————————");
        mPrice.setText("——");
        mDiscount.setText("———");
        mImage.setImageURI(null);
        mPictures.clear();
        mSkuView.removeAllViews();
        mTagsView.removeAllViews();
        mLinkGoodsView.clear();

        /**
         * 釋放主圖資源
         */
        Fresco.getImagePipeline().evictFromMemoryCache(Uri.parse(getTitleImage()));

        /**
         * 釋放詳情圖列表資源
         */
        for(int i=0; i<mSaleDetailCopy.getImages().length; i++){
            Fresco.getImagePipeline().evictFromMemoryCache(Uri.parse(mSaleDetailCopy.getImages()[i]));
        }

        /**
         * 釋放相關推薦列表資源
         */
        for(int i=0; i<mSaleDetailCopy.getLink_goods().size(); i++){
            Fresco.getImagePipeline().evictFromMemoryCache(Uri.parse(mSaleDetailCopy.getLink_goods().get(i).getGoods_img()));
        }

        mSaleDetailCopy.clear();
        mSaleDetailCopy = null;
    }

mPictures.clear()方法內部(因為圖片高度是不一樣的,而SimpleDrawable是不能設定wrap_content自適應網絡圖片,所以這里使用的重寫Linealayout來加載圖,在圖片回調成功后通過動態設置LayoutParams來設置圖片寬高),界面嵌套關系-> LineaLayout -> ImageViews

    public void clear(){
        for(int i=0; i<getChildCount(); i++){
            if(getChildAt(i) instanceof ViewGroup){
                ((ViewGroup)getChildAt(i)).removeAllViews();
            }
        }
        removeAllViews();
    }

mLinkGoodsView.clear()

    public void clear(){
        if(mLinkGoodsScrollView != null){
            mLinkGoodsScrollView.clear();
            removeAllViews();
        }
    }

mLinkGoodsScrollView.clear()

    public void clear(){
        if(mContent != null){
            mContent.removeAllViews();
            removeView(mContent);
        }
    }

那我們在什么地方調用呢?如果是Activity不可見就釋放資源,我們可以在onStop方法中調用,如果是監聽內存回調接口,我們可以在onTrimMemory方法調用,這里兩種都寫上

    @Override
    public void onTrimMemory(int level) {
        if(level == TRIM_MEMORY_UI_HIDDEN){
            doOnStop();
        }
    }

    @Override
    protected void doOnStop() {
        CURRENT_STATE |= READY_IN_STOP;
         getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {
                if((CURRENT_STATE & READY_IN_STOP) == READY_IN_STOP){
                    getCategory().clear();
                    CURRENT_STATE |= IN_STOP;
                }
            }
        }, 250);
    }

界面再次可見時,我們要恢復界面,以下代碼在Category中

    public void onResume(){
        /**
         * 拿一個當前的界面view做判斷,是否被回收了
         * 如果mAnimationOver == true 說明詳情頁動畫已經加載過了,不是第一次加載界面
         */
        if(mSkuView.getChildCount() <=1 && mAnimationOver){
            /**
             * 請求回調的接口方法
             */
            getSaleDetailSucess(mSaleDetail);
        }
    }

Activity中的調用

    @Override
    protected void doOnResume() {
        if(!firstRunning && (CURRENT_STATE & IN_STOP) == IN_STOP){
            CURRENT_STATE &= ~READY_IN_STOP;
            CURRENT_STATE &= ~IN_STOP;
            getCategory().onResume();
        }
        if((CURRENT_STATE & READY_IN_STOP) == READY_IN_STOP){
            CURRENT_STATE &= ~READY_IN_STOP;
        }
    }

以上位運算僅僅是區分Activity生命周期回調的順序,防止用戶快速點擊、返回而Runnable未執行完成,導致界面被回收
前面貼過的詳情頁連續加載的效果圖


詳情頁加載退出的效果

那么在列表頁,如何回收內存呢

    public void clear(){

        if(mDetail == null){
            return;
        }

        /**
         * 釋放標題圖
         */
        Fresco.getImagePipeline().evictFromMemoryCache(Uri.parse(mDetail.getImage()));

        mZoomImage.setImageURI(null);

        /**
         * 將Adapter容器滯空
         */
        if(mAdapter != null && mAdapter.getItemCount()>0){
            mAdapter.notifyDataSetChangedNull();
        }

        /**
         * 清空列表圖片資源
         */
        for(int i=0; i<mAdapter.getItemCount(); i++){
            Fresco.getImagePipeline().evictFromMemoryCache(Uri.parse(mAdapter.getItem(i).getGoods_img()));
        }

    }

Adapter中是如何寫的(這里涉及到一個參數傳值的基礎知識,因為方法內傳遞參數如果是引用類型,那傳遞是引用對象地址的拷貝值,所以重新復制新集合不會覆蓋外部對象的引用地址)

   public void notifyDataSetChangedNull(){
        this.datas = new ArrayList();
        this.notifyDataSetChanged();
    }

重新加載就比較簡單了

    public void onResume(){
        if(mAdapter.getItemCount() == 0 && mDetail != null){
             getThemeDetailSuccess(mDetail);
        }
    }

上一張列表頁釋放和加載的效果圖(回退主題列表頁是能看到圖片再次被加載的瞬間的)

主題列表頁的資源釋放

內存波動圖(注意有兩個曲線下降的折線點,雖然后面內存降低的內存占用比0s - 5s要高,但是內存下降的地方并非退出主題列表界面,而是打開了商品詳情,也就是我們打開商品詳情并沒有導致內存上升,反而是把主題列表的圖片釋放了再加載詳情頁所以內存有降低,而后面那個更低的是詳情頁退出,也就是我們釋放主題列表的資源是成功的),雖然退出詳情頁本身也是會有內存波動的,但是這里更多的是突出主題詳情到商品詳情跳轉的波動不同

內存波動曲線

經過優化后的簡物實測幾次最高打開到170個界面沒有卡頓的感覺,后退界面均可重新加載

至此簡物中性能優化過程就暫時告一段落了,這篇文章沒有梳理,如果寫的凌亂或者有錯誤的地方請在評論中指正出來,我會做出相應改正,謝謝!另外寫文章不易,希望不要隨意轉載,尊重版權,需要轉載請私信我,同意后標明來源即可

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,666評論 25 708
  • Android作為一種移動設備,無論是內存還是CPU性能都會有一定的限制,無法和PC設備相比擬,有鑒于此,Andr...
    乆丩乣閱讀 827評論 1 21
  • 性能優化系列閱讀 Android性能優化 性能優化 - 消除卡頓 性能優化- 內存優化 性能分析工具 - Trac...
    JackChen1024閱讀 1,332評論 1 20
  • 周星馳的電影《功夫》里面借火云邪神之口說出了一句至理名言:“天下武功,唯快不破”。 在移動互聯網時代,同樣如此,如...
    lipy_閱讀 968評論 0 2
  • 無可奈何一歲去,與君相識二十載。往者不可諫,來者憂可追。東隅未逝,桑榆有足。時逢桃李年華,愿執篤行以好學,勤修學而...
    小曉笑_f081閱讀 369評論 0 1