Android 性能優(yōu)化篇之--復雜listView高效渲染

列表是APP必用功能洒琢,Item多了,會使App內(nèi)存占用升高褐桌,于是有了ViewHolder對每個重用Item進行緩存衰抑。但是在復雜的數(shù)據(jù)類型中:新聞、圖片荧嵌、網(wǎng)頁鏈接呛踊、視頻砾淌、視頻+文字、文字加圖片谭网、轉發(fā)+文字等等汪厨,這種情況下還要添加邏輯去緩存各種類型的View,同樣的處理不好愉择,App內(nèi)存占用過高劫乱,列表卡頓,這里我就寫寫我以前的各種優(yōu)化心得锥涕。

07.jpg
一衷戈、ViewHolder原理:重用View和減少Child View查找時間

先看一下BaseAdapter默認重新方法

    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;
    }

其中getView是渲染每個Item時進行回調(diào)生成View的,方法參數(shù)convertView就是ListView傳回可以復用的View层坠,當其不為null時殖妇,無需重新創(chuàng)建View,可以直接使用convertView破花,進行數(shù)據(jù)渲染即可拉一。其原理是當?shù)谝淮握{(diào)用時ListView直接將生成的View緩存到一個ArrayList<View>中,當需要時直接從ArrayList中取出即可:

二旧乞、多類型Item

多類型Item時蔚润,BaseAdapter提供了兩個方法用來返回不同類型

    @Override
    //返回view類型數(shù)量
    public int getViewTypeCount() {
        return super.getViewTypeCount();
    }

    @Override
    //返回每個Item的類型
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }
開發(fā)場景

在Android開發(fā)中,可能會遇到一個可滾動且布局比較復雜的界面尺栖,但它并不是一個純粹的List嫡纠,類似如下圖:

Paste_Image.png

通常實現(xiàn)方法可以直接用一個ScrollView將所有內(nèi)容包起來,里面是列表的部分在代碼中用動態(tài)添加布局的方式實現(xiàn)延赌;或者外層ScrollView除盏,里面列表部分用ListView(或RecyclerView)實現(xiàn),但這樣需要解決滑動沖突問題(有時并不能很好解決)

思路

將整個頁面的劃分為不同的item挫以,并處理不同的數(shù)據(jù)模塊者蠕,使代碼更加模塊化,直觀而且更容易維護掐松。其中HomeAdapter是處理List不同item的適配器踱侣,相對于普通適配器多了一個getItemViewType()方法的處理;ImageAdapter 是圖片輪播適配器大磺;HomeItem是整個頁面的數(shù)據(jù)模型抡句,包含了所有item的不同數(shù)據(jù)模型,接收到網(wǎng)絡數(shù)據(jù)時需要對數(shù)據(jù)加工再設置到HomeItem杠愧,然后根據(jù)ItemType 作為不同item類型的判斷待榔,再根據(jù)不同item獲取對應的字段;各個item的數(shù)據(jù)處理是在單獨一個ViewHolder上處理

public class HomeAdapter extends BaseAdapter{
    private Context context;
    private List<HomeItem> homeItemList;
    private final static int SIGN_MALL=0;
    private final static int TAG=1;
    private final static int SPECIAL=2;
    private final static int AD=3;
    private final static int MENU=4;
    private final static int MEAL_SHOW=5;
    private final static int TALENT_SHOW=6;

    public HomeAdapter(Context context, List<HomeItem> homeItemList){
        this.context=context;
        this.homeItemList=homeItemList;
    }

    @Override
    public int getCount(){
        return homeItemList.size();          //頭部4個流济,廣告位3個
    }

    @Override
    public Object getItem(int position){
        return homeItemList== null ? null : homeItemList.get(position);
    }

    @Override
    public long getItemId(int position){
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup){
        HomeItem homeItem=homeItemList.get(position);
        LayoutInflater inflater=LayoutInflater.from(context);
        SignMallHolder signMallHolder;
        TagHolder tagHolder;
        SpecialHolder specialHolder;
        MenuHolder menuHolder;
        AdHolder adHolder;
        MealShowHolder mealShowHolder;
        TalentShowHolder talentShowHolder;
        int type=homeItem.getItemType().getValue();
        switch(type){
            case SIGN_MALL:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_sign_mall,null);
                    signMallHolder=new SignMallHolder(convertView);
                    convertView.setTag(signMallHolder);
                }else{
                    signMallHolder=(SignMallHolder)convertView.getTag();
                }
                break;
            case TAG:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_tag,null);
                    tagHolder=new TagHolder(convertView);
                    convertView.setTag(tagHolder);
                }else{
                    tagHolder=(TagHolder)convertView.getTag();
                }
                tagHolder.refreshUI(homeItem);
                break;
            case SPECIAL:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_special,null);
                    specialHolder=new SpecialHolder(convertView);
                    convertView.setTag(specialHolder);
                }else{
                    specialHolder=(SpecialHolder)convertView.getTag();
                }
                specialHolder.refreshUI(homeItem);
                break;
            case AD:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_ad,null);
                    adHolder=new AdHolder(context,convertView);
                    convertView.setTag(adHolder);
                }else{
                    adHolder=(AdHolder)convertView.getTag();
                }
                adHolder.setViewPager(homeItem);
                break;
            case MENU:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_menu,null);
                    menuHolder=new MenuHolder(convertView);
                    convertView.setTag(menuHolder);
                }else{
                    menuHolder=(MenuHolder)convertView.getTag();
                }
                menuHolder.refreshUI(homeItem);
                break;
            case MEAL_SHOW:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_meal_show,null);
                    mealShowHolder=new MealShowHolder(context,convertView);
                    convertView.setTag(mealShowHolder);
                }else{
                    mealShowHolder=(MealShowHolder)convertView.getTag();
                }
                mealShowHolder.setViewPager(homeItem);
                break;
            case TALENT_SHOW:
                if(convertView==null){
                    convertView=inflater.inflate(R.layout.view_home_talent,null);
                    talentShowHolder=new TalentShowHolder(context,convertView);
                    convertView.setTag(talentShowHolder);
                }else{
                    talentShowHolder=(TalentShowHolder)convertView.getTag();
                }
                talentShowHolder.initView(homeItem);
                break;
        }
        return convertView;
    }

    @Override
    public int getItemViewType(int position){
        if (homeItemList!= null && position < homeItemList.size()) {
            return homeItemList.get(position).getItemType().getValue();
        }
        return super.getItemViewType(position);
    }

    @Override
    public int getViewTypeCount(){
        return 7;
    }
}
public class ImageAdapter extends PagerAdapter{
    private List<ImageView> imgList=new ArrayList<ImageView>();

    public ImageAdapter(Context context,int[] imgIds) {
        for(int i=0;i<imgIds.length;i++){
            ImageView imageView=new ImageView(context);
            imageView.setImageResource(imgIds[i]);
            imgList.add(imageView);
        }
    }

    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    private OnItemClickListener onItemClickListener;

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public int getCount() {
        //設置成最大锐锣,使用戶看不到邊界
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0==arg1;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        //Warning:不要在這里調(diào)用removeView
    }

    @Override
    public Object instantiateItem(final ViewGroup container, int position) {
        //對ViewPager頁號求模取出View列表中要顯示的項
        position %= imgList.size();
        if (position<0){
            position = imgList.size()+position;
        }
        final ImageView view = imgList.get(position);
        //如果View已經(jīng)在之前添加到了一個父組件腌闯,則必須先remove,否則會拋出IllegalStateException雕憔。
        ViewParent vp =view.getParent();
        if (vp!=null){
            ViewGroup parent = (ViewGroup)vp;
            parent.removeView(view);
        }
        container.addView(view);
        //add listeners here if necessary
        final int positionId=position;
        if (onItemClickListener != null) {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = positionId;
                    onItemClickListener.onItemClick(view, pos);
                }
            });
        }

        return view;
    }
}

ComplexListViewDemo

三绑嘹、NotifyDataSetChanged刷新機制

當ListView中的數(shù)據(jù)發(fā)生了改變,我們希望刷新ListView中的View時橘茉,我們一般會調(diào)用NotifyDataSetChanged來刷新ListView工腋。看一下它的源碼:

public void notifyChanged() {   
    synchronized (mObservers) {   
        // 向每一個子View發(fā)送onChanged   
        for (int i = mObservers.size() - 1; i >= 0; i--) {   
            mObservers.get(i).onChanged();   
        }   
    }   
} 

發(fā)現(xiàn)它針對每一個子View都做了刷新畅卓,當然擅腰,如果我們的數(shù)據(jù)都變量還可以理解。但是翁潘,一般條件下趁冈,我們需要更新的View不多。頻繁的調(diào)用NotifyDataSetChanged方法拜马,刷新整個界面不合適渗勘。這樣會把界面上顯示的所有item都全部重繪一次,即使只有一個view的內(nèi)容發(fā)生 了變化俩莽。
所以旺坠,我們可以寫一個update的方法,來單獨刷新一個View

private void updateView(int itemIndex){   
    intvisiblePosition = yourListView.getFirstVisiblePosition();   
    Viewv = yourListView.getChildAt(itemIndex - visiblePosition);   
         ViewHolder viewHolder =(ViewHolder)v.getTag();   
         if(viewHolder!= null){   
               viewHolder.titleTextView.setText("我更新了");   
         }      
} 
四扮超、多層嵌套列表的優(yōu)化取刃。

有這樣的場景:如QQ列表層級是2,這時候我們會使用ExpandableListView來展示出刷。剛好ExpandableListView還可以收縮和展開璧疗。但是ExpandableListView只能展示兩層,遇到層級更復雜的數(shù)據(jù)馁龟,就不太適用了崩侠。如果遇到多層級的優(yōu)化,應該怎么做坷檩?

1 將數(shù)據(jù)源層層遍歷却音,添加到列表或者數(shù)組中,用ListView展示淌喻。

這是非常直觀的做法僧家,但仍有局限性。
一是展平后的數(shù)據(jù)和原始數(shù)據(jù)失去了關聯(lián)裸删,如果單純的展示數(shù)據(jù)還好,如果需要操作數(shù)據(jù),操作起來就比較麻煩阵赠。
二是展示復雜數(shù)據(jù)的場景太多了涯塔,經(jīng)常為特定的場景寫類似的代碼很麻煩肌稻,而且數(shù)據(jù)不同,寫法也不一樣匕荸,每次都要為類似的事情重新構思爹谭,是不是很煩?

解決思路

用一個Node類表示樹節(jié)點榛搔,用來構造樹诺凡,Node類需要維護一個int類型的數(shù)量,表示這個節(jié)點包含的子節(jié)點數(shù)(包括子節(jié)點的子節(jié)點的子節(jié)點..践惑,也就是節(jié)點展平后的數(shù)量),這樣腹泌,就能方便地在索引中添加和刪除節(jié)點的引用了(因為在索引中添加和刪除節(jié)點需要同時處理其子節(jié)點)。

demo代碼:https://github.com/jack-cook/HierarchicalViewSample
2480008-b99790ec7222baa0.gif
五尔觉、總結
1 觀察多種類型的Item凉袱,并找出他們的相同點:并拆分可以單獨進行復用的模塊,這樣可以使緩存ArrayList中保存的View數(shù)量減少侦铜,內(nèi)存消耗減了不少专甩。
2 盡可能減少布局層次
3 只刷新變化的部分View
4 避免調(diào)用addView這樣的方法
5 首次加載圖片就處理(圓角/縮放等)并緩存在本地
6 只加載當前視圖需要的圖片,并且在滑動列表的時候停止后臺的加載線程,為UI線程空出cpu資源钉稍,在停止的時候再請求涤躲。
7 盡量使用RecyclerView代替ListView: 每個item內(nèi)容的變動,listview都需要去調(diào)用notifyDataSetChanged來更新全部的item贡未,太浪費性能了篓叶。RecycleView可以實現(xiàn)當個item的局部刷新,并且引入了增加和刪除的動態(tài)效果羞秤,在性能上和定制上都有很大的改善缸托。
8 盡量能保證 Adapter 的 hasStableIds() 返回 true 這樣在 notifyDataSetChanged() 的時候,如果item內(nèi)容并沒有變化瘾蛋,ListView 將不會重新繪制這個 View俐镐,達到優(yōu)化的目的
9 ListView 中元素避免半透明: 半透明繪制需要大量乘法計算,在滑動時不停重繪會造成大量的計算哺哼,在比較差的機子上會比較卡佩抹。 在設計上能不半透明就不不半透明。實在要弄就把在滑動的時候把半透明設置成不透明取董,滑動完再重新設置成半透明棍苹。
10 避免在getView方法中做耗時操作。

上面各種優(yōu)化之后茵汰,運行程序枢里,觀察前后的效果,內(nèi)存占用可以減少10~20m,滑動流暢度也提高不少栏豺,在低端手機上的效果尤其明顯彬碱,掉幀明顯減少。非常建議有需要的同學嘗試奥洼。


另外網(wǎng)上性能總結

1巷疼,在Activity,Fragment等生命周期方法中和Adapter重寫類中,避免有些頻繁觸發(fā)的邏輯方法中存在大量對象分配

2灵奖,懶加載和緩存機制嚼沿。訪問網(wǎng)絡的耗時操作啟動一個新線程來做,而不要再UI線程來做瓷患,單例最好懶加載骡尽,F(xiàn)ragment也最好懶加載

3,UI線程不做耗時操作尉尾,耗時操作放在子線程處理

4爆阶,布局文件要盡可能的優(yōu)化,減少布局的解析時間沙咏。盡量減少布局的嵌套層次辨图,盡量使用include,merge肢藐,ViewStub

5故河,減少同一時刻的動畫執(zhí)行次數(shù)

6,自定義view時吆豹,減少onMeasure鱼的,onLayout,onDraw等的調(diào)用次數(shù)痘煤,注意避免有些頻繁觸發(fā)的邏輯方法中存在大量對象分配

7凑阶,對象引用之后要及時回收

8,減少冗余資源和代碼邏輯的使用

9衷快,減少沒必要的背景宙橱、暫時不顯示的View設置為GONE而不是INVISIBLE、自定義View的onDraw方法設置canvas.clipRect()指定繪制區(qū)域或通過canvas.quickreject()減少繪制區(qū)域等蘸拔。

10师郑,盡量避免在多次for循環(huán)中頻繁分配對象

11,避免在自定義View的onDraw()方法中執(zhí)行復雜的操作及創(chuàng)建對象(譬如Paint的實例化操作不要寫在onDraw()方法中等)

12调窍,對于并發(fā)下載等類似邏輯的實現(xiàn)盡量避免多次創(chuàng)建線程對象宝冕,而是交給線程池處理

13,使用foreach代替for i

14邓萨,盡量少的聲明全局變量

15地梨,聲明全局靜態(tài)變量菊卷,一定要加final聲明

16,聲明非靜態(tài)的全局變量湿刽,最好不要初始化任何值的烁,在使用到的地方褐耳,在進行初始化

17诈闺,函數(shù)中若干次使用全局變量,應該將全局變量賦值給本地變量铃芦,然后直接使用本地變量

18雅镊,能用Int,不要使用浮點數(shù)

19刃滓,能用乘法不用除法

20仁烹,盡量避免使用geter和setter方法

21,在Activity的onCreate函數(shù)中咧虎,盡量做少的事

22卓缰,在Activity中聲明的靜態(tài)數(shù)組或者靜態(tài)代碼塊,重構到單獨的一個類里

23砰诵,Activity啟動后開始進行異步線程的加載征唬,最好delay一下。再開啟線程

24茁彭,對于存在于集合中的Bean對象总寒,盡可能少的聲明變量。能用int 就不要用long.聲明的string等復雜變量理肺,最好不要進行初始化

25摄闸,使用線程,一定要給它傳一個名字妹萨,然后需要定義線程的優(yōu)先級

26年枕,在使用集合的時候,優(yōu)先選擇SparseArray

27乎完,盡量避免使用枚舉

28熏兄,工具方法盡量寫成是靜態(tài)方法

29,線程間同步盡量使用開銷小的同步鎖

30囱怕,在使用集合類的時候霍弹,如果已知數(shù)據(jù)的規(guī)模,在初始化的時候娃弓,就設定好默認大小

31典格,私有內(nèi)部類訪問外部類的私有變量,要將變量修改為包繼承權限台丛,在私有內(nèi)部類中耍缴,考慮用包訪問權限替代私有訪問權限

32砾肺,對于開銷大的算法,且不止是執(zhí)行一次的防嗡,要使用緩存策略

33变汪,避免在繪制或者解析布局的時候,分配對象蚁趁。例如onDraw方法

34裙盾,不要給布局寫無用的參數(shù),例如RelativeLayout他嫡,寫layout_weight屬性

35番官,盡量減少布局的嵌套層數(shù)。例如包含一個ImageView和TextView的線性布局钢属,可以用CompoundDrawable的TextView來代替

36徘熔,盡量用Android提供的SparseArray來代替HashMap

37,如果LinearLayout用于嵌套的layout空間計算淆党,它的android:baselineAligned設置為false,可以加速layout計算

38酷师,盡量避免嵌套的使用layout_weight,那樣會影響執(zhí)行效率

39,如果為rootView設置了背景染乌,那么會先用Theme指定的背景繪制一遍徙鱼,然后才用指定的背景繪制信卡,這叫做"overdraw",可以通過theme的background為null來避免

40溅潜,不要有無用的任何資源阅仔,代碼或者文件

41,一個Activity中使用同一個View.onClickListener()處理所有的業(yè)務邏輯

42台谊,數(shù)據(jù)一定要校驗蓉媳,如用戶填寫的日期時間數(shù)據(jù)、電話號碼數(shù)據(jù)等

43锅铅,不要隨意的使用stingA=StringB+StringC的寫法酪呻,有大量拼接操作的地方用StringBuilder代替

44,有些能用文件操作的盐须,盡量采用文件操作玩荠,文件操作的速度比數(shù)據(jù)庫的操作要快10倍左右

45,避免重復點擊和快速點擊

46贼邓,盡量避免static成員變量引用資源耗費過多的實例,比如Context

47阶冈,應用開發(fā)中自定義View的時候,交互部分塑径,千萬不要寫成線程不斷刷新界面顯示女坑,而是根據(jù)TouchListener事件主動觸發(fā)界面的更新

48,如果ImageView的圖片是來自網(wǎng)絡统舀,進行異步加載

49匆骗,.保證Cursor 占用的內(nèi)存被及時的釋放掉劳景,而不是等待GC來處理。并且 Android明顯是傾向于編 程者手動的將Cursor close掉

50碉就,軟鍵盤的彈出控制盟广,不要讓其覆蓋輸入框

51,使用styles瓮钥,復用樣式定義

52筋量,復雜布局使用RelativeLayout

53,自適應屏幕骏庸,使用dp替代pix

54毛甲,使用animation-list制作動畫效果

官網(wǎng)規(guī)范

記得關閉啟動的服務
當服務中的任務完成后年叮,要記得停止該服務具被。可以考慮使用 IntentService只损,因為IntentService 在完成任務后會自動停止一姿。

UI 不可見時釋放資源
在 onStop 中關閉網(wǎng)絡連接、注銷廣播接收器跃惫、釋放傳感器等資源叮叹;

在 onTrimMemory() 回調(diào)方法中監(jiān)聽TRIM_MEMORY_UI_HIDDEN 級別的信號,此時可在 Activity 中釋放 UI 使用的資源爆存,大符減少應用占用的內(nèi)存蛉顽,從而避免被系統(tǒng)清除出內(nèi)存。

內(nèi)存緊張時釋放資源
運行中的程序先较,如果內(nèi)存緊張携冤,會在 onTrimMemory(int level) 回調(diào)方法中接收到以下級別的信號:

TRIM_MEMORY_RUNNING_MODERATE:系統(tǒng)可用內(nèi)存較低,正在殺掉 LRU緩存中的進程闲勺。你的進程正在運行曾棕,沒有被殺掉的危險。

TRIM_MEMORY_RUNNING_LOW:系統(tǒng)可用內(nèi)存更加緊張菜循,程序雖然暫沒有被殺死的危險翘地,但是應該盡量釋放一些資源,以提升系統(tǒng)的性能(這也會直接影響你程序的性能)癌幕。

TRIM_MEMORY_RUNNING_CRITICAL:系統(tǒng)內(nèi)存極度緊張衙耕,而LRU緩存中的大部分進程已被殺死,如果仍然無法獲得足夠的資源的話勺远,接下來會清理掉 LRU 中的所有進程橙喘,并且開始殺死一些系統(tǒng)通常會保留的進程,比如后臺運行的服務等谚中。

當程序未在運行渴杆,保留在 LRU 緩存中時寥枝, onTrimMemory(int level) 中會返回以下級別的信號:

TRIM_MEMORY_BACKGROUND:系統(tǒng)可用內(nèi)存低,而你的程序處在 LRU的頂端磁奖,因此暫時不會被殺死囊拜,但是此時應釋放一些程序再次打開時比較容易恢復的 UI 資源。

TRIM_MEMORY_MODERATE:系統(tǒng)可用內(nèi)存低比搭,程序處于 LRU的中部位置冠跷,如果內(nèi)存狀態(tài)得不到緩解,程序會有被殺死的可能身诺。

TRIM_MEMORY_COMPLETE:系統(tǒng)可用內(nèi)存低蜜托,你的程序處于 LRU尾部,如果系統(tǒng)仍然無法回收足夠的內(nèi)存資源霉赡,你的程序將首先被殺死橄务。此時應釋放無助于恢復程序狀態(tài)的所有資源。

注:該 API 在版本 14 中加入穴亏。舊版本的onLowMemory() 方法蜂挪,大致相當于 onTrimMemory(int level) 中接收到 TRIM_MEMORY_COMPLETE 級別的信號。

另:盡管系統(tǒng)主要按照 LRU 中順序來殺進程嗓化,不過系統(tǒng)也會考慮程序占用的內(nèi)存多少棠涮,那些占用內(nèi)存高的進程有更高的可能性會被首先殺死。

確定你的程序應該占用多少內(nèi)存
可以通過 getMemoryClass()來獲取你的程序被分配的可用內(nèi)存刺覆,以 M 為單位严肪。

你可以通過在 <application> 標簽下將 largeHeap 屬性設為 true 來要求更多的內(nèi)存,這時通過 getLargeMemoryClass() 方法來獲取可用內(nèi)存谦屑。

大部分應用程序不需要使用此功能驳糯,因此使用該標簽前,確認你的程序是否真的需要更多內(nèi)存伦仍。使用更多內(nèi)存會對整個系統(tǒng)的性能產(chǎn)生影響结窘,而且當程序進入 LRU時會更容易首先被系統(tǒng)清理掉。

正確使用 Bipmap充蓝,避免浪費內(nèi)存
如果你的 ImageViwe 的尺寸只有 100 100隧枫,那么沒有必要將一張 2560 1600 的圖片整個加載入內(nèi)存。

使用 Android提供的優(yōu)化過的數(shù)據(jù)結構
如 SparseArray谓苟, SparseBooleanArray官脓, LongSparseArray 等,相比 Java 提供的 HashMap涝焙,這些結構更節(jié)省內(nèi)存卑笨。

始終對內(nèi)存使用情況保持關注
枚舉類型 Enum 會比靜態(tài)常量占用更多的內(nèi)存;

Java 中每個類(包括匿名內(nèi)部類)都占用至少 500字節(jié)左右的代碼仑撞;

每個類的實例會在 RAM 中占用大約 12 ~ 16 字節(jié)的內(nèi)存赤兴;

每向 HashMap 中添加一個 Entry 時妖滔,新生成的 Entry 占用大約 32 個字節(jié)。

謹慎使用第三方類庫
這些外部類庫可能原先并非針對移動平臺桶良,因此未進行過優(yōu)化座舍,在使用前應注意。另外盡量不要因為一兩個特性而使用一個體積很大的類庫陨帆。

使用 ProGuard
使用 ProGuard 移除無用的代碼并重命名一些類曲秉、字段、方法等疲牵,使你的代碼更緊湊承二,節(jié)省內(nèi)存空間。

使用 zipalign
zipaligned 對最終打包的 apk進行字節(jié)對齊纲爸。

注:Google Play 不接受未對齊過的 apk。

分析內(nèi)存使用情況
如果已經(jīng)獲得一個相對穩(wěn)定的版本缩焦,應對程序整個生命周期的內(nèi)存使用狀況進行分析读虏。

使用多個進程
如果程序需要執(zhí)行大量的后臺工作,可考慮將程序分為兩個進程袁滥,一個進程負責 UI,另一個進程負責后臺任務灾螃。比如音樂播放器题翻。

代碼示例:

<serviceandroid:name=".PlaybackService"android:process=":background"/>

android:process屬性的值以“:”開頭,名稱可任意選取腰鬼。

在決定是否使用多進程前嵌赠,應注意,一個不執(zhí)行任何任務的空進程至少也要占用 1.4 MB內(nèi)存熄赡。

另外要注意進程的相互依賴性姜挺,比如如果將 ContentProvider 放在 UI 進程中,而后臺任務進程也需要調(diào)用 ContentProvider彼硫,就會導致 UI 進程一直保留在 RAM 中炊豪。

參考文章

Android ListView工作原理完全解析,帶你從源碼的角度徹底理解拧篮,androidlistviewhttp://www.android100.org/html/201507/26/168809.htmlhttp://android.jobbole.com/81834/
MultiTypeDemo

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末词渤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子串绩,更是在濱河造成了極大的恐慌缺虐,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件礁凡,死亡現(xiàn)場離奇詭異高氮,居然都是意外死亡慧妄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門剪芍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腰涧,“玉大人,你說我怎么就攤上這事紊浩〗颜。” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵坊谁,是天一觀的道長费彼。 經(jīng)常有香客問我,道長口芍,這世上最難降的妖魔是什么箍铲? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鬓椭,結果婚禮上颠猴,老公的妹妹穿的比我還像新娘。我一直安慰自己小染,他們只是感情好翘瓮,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裤翩,像睡著了一般资盅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上踊赠,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天呵扛,我揣著相機與錄音,去河邊找鬼筐带。 笑死今穿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的伦籍。 我是一名探鬼主播蓝晒,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸽斟!你這毒婦竟也來了拔创?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤富蓄,失蹤者是張志新(化名)和其女友劉穎剩燥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡灭红,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年侣滩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片变擒。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡君珠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出娇斑,到底是詐尸還是另有隱情策添,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布毫缆,位于F島的核電站唯竹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏苦丁。R本人自食惡果不足惜浸颓,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旺拉。 院中可真熱鬧产上,春花似錦、人聲如沸蛾狗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淘太。三九已至姻僧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒲牧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工赌莺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冰抢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓艘狭,卻偏偏與公主長得像挎扰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巢音,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容