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

一、ViewHolder原理:重用View和減少Child View查找時間
先看一下BaseAdapter默認(rèn)重新方法
@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;
}
其中g(shù)etView是渲染每個Item時進(jìn)行回調(diào)生成View的,方法參數(shù)convertView就是ListView傳回可以復(fù)用的View,當(dāng)其不為null時,無需重新創(chuàng)建View,可以直接使用convertView,進(jìn)行數(shù)據(jù)渲染即可。其原理是當(dāng)?shù)谝淮握{(diào)用時ListView直接將生成的View緩存到一個ArrayList<View>中,當(dāng)需要時直接從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ā)中,可能會遇到一個可滾動且布局比較復(fù)雜的界面,但它并不是一個純粹的List,類似如下圖:

通常實(shí)現(xiàn)方法可以直接用一個ScrollView將所有內(nèi)容包起來,里面是列表的部分在代碼中用動態(tài)添加布局的方式實(shí)現(xiàn);或者外層ScrollView,里面列表部分用ListView(或RecyclerView)實(shí)現(xiàn),但這樣需要解決滑動沖突問題(有時并不能很好解決)
思路
將整個頁面的劃分為不同的item,并處理不同的數(shù)據(jù)模塊,使代碼更加模塊化,直觀而且更容易維護(hù)。其中HomeAdapter是處理List不同item的適配器,相對于普通適配器多了一個getItemViewType()方法的處理;ImageAdapter 是圖片輪播適配器;HomeItem是整個頁面的數(shù)據(jù)模型,包含了所有item的不同數(shù)據(jù)模型,接收到網(wǎng)絡(luò)數(shù)據(jù)時需要對數(shù)據(jù)加工再設(shè)置到HomeItem,然后根據(jù)ItemType 作為不同item類型的判斷,再根據(jù)不同item獲取對應(yīng)的字段;各個item的數(shù)據(jù)處理是在單獨(dú)一個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() {
//設(shè)置成最大,使用戶看不到邊界
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列表中要顯示的項(xiàng)
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;
}
}
三、NotifyDataSetChanged刷新機(jī)制
當(dāng)ListView中的數(shù)據(jù)發(fā)生了改變,我們希望刷新ListView中的View時,我們一般會調(diào)用NotifyDataSetChanged來刷新ListView??匆幌滤脑创a:
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都做了刷新,當(dāng)然,如果我們的數(shù)據(jù)都變量還可以理解。但是,一般條件下,我們需要更新的View不多。頻繁的調(diào)用NotifyDataSetChanged方法,刷新整個界面不合適。這樣會把界面上顯示的所有item都全部重繪一次,即使只有一個view的內(nèi)容發(fā)生 了變化。
所以,我們可以寫一個update的方法,來單獨(dú)刷新一個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只能展示兩層,遇到層級更復(fù)雜的數(shù)據(jù),就不太適用了。如果遇到多層級的優(yōu)化,應(yīng)該怎么做?
1 將數(shù)據(jù)源層層遍歷,添加到列表或者數(shù)組中,用ListView展示。
這是非常直觀的做法,但仍有局限性。
一是展平后的數(shù)據(jù)和原始數(shù)據(jù)失去了關(guān)聯(lián),如果單純的展示數(shù)據(jù)還好,如果需要操作數(shù)據(jù),操作起來就比較麻煩。
二是展示復(fù)雜數(shù)據(jù)的場景太多了,經(jīng)常為特定的場景寫類似的代碼很麻煩,而且數(shù)據(jù)不同,寫法也不一樣,每次都要為類似的事情重新構(gòu)思,是不是很煩?
解決思路
用一個Node類表示樹節(jié)點(diǎn),用來構(gòu)造樹,Node類需要維護(hù)一個int類型的數(shù)量,表示這個節(jié)點(diǎn)包含的子節(jié)點(diǎn)數(shù)(包括子節(jié)點(diǎn)的子節(jié)點(diǎn)的子節(jié)點(diǎn)..,也就是節(jié)點(diǎn)展平后的數(shù)量),這樣,就能方便地在索引中添加和刪除節(jié)點(diǎn)的引用了(因?yàn)樵谒饕刑砑雍蛣h除節(jié)點(diǎn)需要同時處理其子節(jié)點(diǎn))。
demo代碼:https://github.com/jack-cook/HierarchicalViewSample

五、總結(jié)
1 觀察多種類型的Item,并找出他們的相同點(diǎn):并拆分可以單獨(dú)進(jìn)行復(fù)用的模塊,這樣可以使緩存ArrayList中保存的View數(shù)量減少,內(nèi)存消耗減了不少。
2 盡可能減少布局層次
3 只刷新變化的部分View
4 避免調(diào)用addView這樣的方法
5 首次加載圖片就處理(圓角/縮放等)并緩存在本地
6 只加載當(dāng)前視圖需要的圖片,并且在滑動列表的時候停止后臺的加載線程,為UI線程空出cpu資源,在停止的時候再請求。
7 盡量使用RecyclerView代替ListView: 每個item內(nèi)容的變動,listview都需要去調(diào)用notifyDataSetChanged來更新全部的item,太浪費(fèi)性能了。RecycleView可以實(shí)現(xiàn)當(dāng)個item的局部刷新,并且引入了增加和刪除的動態(tài)效果,在性能上和定制上都有很大的改善。
8 盡量能保證 Adapter 的 hasStableIds() 返回 true 這樣在 notifyDataSetChanged() 的時候,如果item內(nèi)容并沒有變化,ListView 將不會重新繪制這個 View,達(dá)到優(yōu)化的目的
9 ListView 中元素避免半透明: 半透明繪制需要大量乘法計(jì)算,在滑動時不停重繪會造成大量的計(jì)算,在比較差的機(jī)子上會比較卡。 在設(shè)計(jì)上能不半透明就不不半透明。實(shí)在要弄就把在滑動的時候把半透明設(shè)置成不透明,滑動完再重新設(shè)置成半透明。
10 避免在getView方法中做耗時操作。
上面各種優(yōu)化之后,運(yùn)行程序,觀察前后的效果,內(nèi)存占用可以減少10~20m,滑動流暢度也提高不少,在低端手機(jī)上的效果尤其明顯,掉幀明顯減少。非常建議有需要的同學(xué)嘗試。
另外網(wǎng)上性能總結(jié)
1,在Activity,Fragment等生命周期方法中和Adapter重寫類中,避免有些頻繁觸發(fā)的邏輯方法中存在大量對象分配
2,懶加載和緩存機(jī)制。訪問網(wǎng)絡(luò)的耗時操作啟動一個新線程來做,而不要再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設(shè)置為GONE而不是INVISIBLE、自定義View的onDraw方法設(shè)置canvas.clipRect()指定繪制區(qū)域或通過canvas.quickreject()減少繪制區(qū)域等。
10,盡量避免在多次for循環(huán)中頻繁分配對象
11,避免在自定義View的onDraw()方法中執(zhí)行復(fù)雜的操作及創(chuàng)建對象(譬如Paint的實(shí)例化操作不要寫在onDraw()方法中等)
12,對于并發(fā)下載等類似邏輯的實(shí)現(xiàn)盡量避免多次創(chuàng)建線程對象,而是交給線程池處理
13,使用foreach代替for i
14,盡量少的聲明全局變量
15,聲明全局靜態(tài)變量,一定要加final聲明
16,聲明非靜態(tài)的全局變量,最好不要初始化任何值,在使用到的地方,在進(jìn)行初始化
17,函數(shù)中若干次使用全局變量,應(yīng)該將全局變量賦值給本地變量,然后直接使用本地變量
18,能用Int,不要使用浮點(diǎn)數(shù)
19,能用乘法不用除法
20,盡量避免使用geter和setter方法
21,在Activity的onCreate函數(shù)中,盡量做少的事
22,在Activity中聲明的靜態(tài)數(shù)組或者靜態(tài)代碼塊,重構(gòu)到單獨(dú)的一個類里
23,Activity啟動后開始進(jìn)行異步線程的加載,最好delay一下。再開啟線程
24,對于存在于集合中的Bean對象,盡可能少的聲明變量。能用int 就不要用long.聲明的string等復(fù)雜變量,最好不要進(jìn)行初始化
25,使用線程,一定要給它傳一個名字,然后需要定義線程的優(yōu)先級
26,在使用集合的時候,優(yōu)先選擇SparseArray
27,盡量避免使用枚舉
28,工具方法盡量寫成是靜態(tài)方法
29,線程間同步盡量使用開銷小的同步鎖
30,在使用集合類的時候,如果已知數(shù)據(jù)的規(guī)模,在初始化的時候,就設(shè)定好默認(rèn)大小
31,私有內(nèi)部類訪問外部類的私有變量,要將變量修改為包繼承權(quán)限,在私有內(nèi)部類中,考慮用包訪問權(quán)限替代私有訪問權(quán)限
32,對于開銷大的算法,且不止是執(zhí)行一次的,要使用緩存策略
33,避免在繪制或者解析布局的時候,分配對象。例如onDraw方法
34,不要給布局寫無用的參數(shù),例如RelativeLayout,寫layout_weight屬性
35,盡量減少布局的嵌套層數(shù)。例如包含一個ImageView和TextView的線性布局,可以用CompoundDrawable的TextView來代替
36,盡量用Android提供的SparseArray來代替HashMap
37,如果LinearLayout用于嵌套的layout空間計(jì)算,它的android:baselineAligned設(shè)置為false,可以加速layout計(jì)算
38,盡量避免嵌套的使用layout_weight,那樣會影響執(zhí)行效率
39,如果為rootView設(shè)置了背景,那么會先用Theme指定的背景繪制一遍,然后才用指定的背景繪制,這叫做"overdraw",可以通過theme的background為null來避免
40,不要有無用的任何資源,代碼或者文件
41,一個Activity中使用同一個View.onClickListener()處理所有的業(yè)務(wù)邏輯
42,數(shù)據(jù)一定要校驗(yàn),如用戶填寫的日期時間數(shù)據(jù)、電話號碼數(shù)據(jù)等
43,不要隨意的使用stingA=StringB+StringC的寫法,有大量拼接操作的地方用StringBuilder代替
44,有些能用文件操作的,盡量采用文件操作,文件操作的速度比數(shù)據(jù)庫的操作要快10倍左右
45,避免重復(fù)點(diǎn)擊和快速點(diǎn)擊
46,盡量避免static成員變量引用資源耗費(fèi)過多的實(shí)例,比如Context
47,應(yīng)用開發(fā)中自定義View的時候,交互部分,千萬不要寫成線程不斷刷新界面顯示,而是根據(jù)TouchListener事件主動觸發(fā)界面的更新
48,如果ImageView的圖片是來自網(wǎng)絡(luò),進(jìn)行異步加載
49,.保證Cursor 占用的內(nèi)存被及時的釋放掉,而不是等待GC來處理。并且 Android明顯是傾向于編 程者手動的將Cursor close掉
50,軟鍵盤的彈出控制,不要讓其覆蓋輸入框
51,使用styles,復(fù)用樣式定義
52,復(fù)雜布局使用RelativeLayout
53,自適應(yīng)屏幕,使用dp替代pix
54,使用animation-list制作動畫效果
官網(wǎng)規(guī)范
記得關(guān)閉啟動的服務(wù)
當(dāng)服務(wù)中的任務(wù)完成后,要記得停止該服務(wù)??梢钥紤]使用 IntentService,因?yàn)镮ntentService 在完成任務(wù)后會自動停止。
UI 不可見時釋放資源
在 onStop 中關(guān)閉網(wǎng)絡(luò)連接、注銷廣播接收器、釋放傳感器等資源;
在 onTrimMemory() 回調(diào)方法中監(jiān)聽TRIM_MEMORY_UI_HIDDEN 級別的信號,此時可在 Activity 中釋放 UI 使用的資源,大符減少應(yīng)用占用的內(nèi)存,從而避免被系統(tǒng)清除出內(nèi)存。
內(nèi)存緊張時釋放資源
運(yùn)行中的程序,如果內(nèi)存緊張,會在 onTrimMemory(int level) 回調(diào)方法中接收到以下級別的信號:
TRIM_MEMORY_RUNNING_MODERATE:系統(tǒng)可用內(nèi)存較低,正在殺掉 LRU緩存中的進(jìn)程。你的進(jìn)程正在運(yùn)行,沒有被殺掉的危險。
TRIM_MEMORY_RUNNING_LOW:系統(tǒng)可用內(nèi)存更加緊張,程序雖然暫沒有被殺死的危險,但是應(yīng)該盡量釋放一些資源,以提升系統(tǒng)的性能(這也會直接影響你程序的性能)。
TRIM_MEMORY_RUNNING_CRITICAL:系統(tǒng)內(nèi)存極度緊張,而LRU緩存中的大部分進(jìn)程已被殺死,如果仍然無法獲得足夠的資源的話,接下來會清理掉 LRU 中的所有進(jìn)程,并且開始?xì)⑺酪恍┫到y(tǒng)通常會保留的進(jìn)程,比如后臺運(yùn)行的服務(wù)等。
當(dāng)程序未在運(yùn)行,保留在 LRU 緩存中時, onTrimMemory(int level) 中會返回以下級別的信號:
TRIM_MEMORY_BACKGROUND:系統(tǒng)可用內(nèi)存低,而你的程序處在 LRU的頂端,因此暫時不會被殺死,但是此時應(yīng)釋放一些程序再次打開時比較容易恢復(fù)的 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)存資源,你的程序?qū)⑹紫缺粴⑺馈4藭r應(yīng)釋放無助于恢復(fù)程序狀態(tài)的所有資源。
注:該 API 在版本 14 中加入。舊版本的onLowMemory() 方法,大致相當(dāng)于 onTrimMemory(int level) 中接收到 TRIM_MEMORY_COMPLETE 級別的信號。
另:盡管系統(tǒng)主要按照 LRU 中順序來殺進(jìn)程,不過系統(tǒng)也會考慮程序占用的內(nèi)存多少,那些占用內(nèi)存高的進(jìn)程有更高的可能性會被首先殺死。
確定你的程序應(yīng)該占用多少內(nèi)存
可以通過 getMemoryClass()來獲取你的程序被分配的可用內(nèi)存,以 M 為單位。
你可以通過在 <application> 標(biāo)簽下將 largeHeap 屬性設(shè)為 true 來要求更多的內(nèi)存,這時通過 getLargeMemoryClass() 方法來獲取可用內(nèi)存。
大部分應(yīng)用程序不需要使用此功能,因此使用該標(biāo)簽前,確認(rèn)你的程序是否真的需要更多內(nèi)存。使用更多內(nèi)存會對整個系統(tǒng)的性能產(chǎn)生影響,而且當(dāng)程序進(jìn)入 LRU時會更容易首先被系統(tǒng)清理掉。
正確使用 Bipmap,避免浪費(fèi)內(nèi)存
如果你的 ImageViwe 的尺寸只有 100 100,那么沒有必要將一張 2560 1600 的圖片整個加載入內(nèi)存。
使用 Android提供的優(yōu)化過的數(shù)據(jù)結(jié)構(gòu)
如 SparseArray, SparseBooleanArray, LongSparseArray 等,相比 Java 提供的 HashMap,這些結(jié)構(gòu)更節(jié)省內(nèi)存。
始終對內(nèi)存使用情況保持關(guān)注
枚舉類型 Enum 會比靜態(tài)常量占用更多的內(nèi)存;
Java 中每個類(包括匿名內(nèi)部類)都占用至少 500字節(jié)左右的代碼;
每個類的實(shí)例會在 RAM 中占用大約 12 ~ 16 字節(jié)的內(nèi)存;
每向 HashMap 中添加一個 Entry 時,新生成的 Entry 占用大約 32 個字節(jié)。
謹(jǐn)慎使用第三方類庫
這些外部類庫可能原先并非針對移動平臺,因此未進(jìn)行過優(yōu)化,在使用前應(yīng)注意。另外盡量不要因?yàn)橐粌蓚€特性而使用一個體積很大的類庫。
使用 ProGuard
使用 ProGuard 移除無用的代碼并重命名一些類、字段、方法等,使你的代碼更緊湊,節(jié)省內(nèi)存空間。
使用 zipalign
zipaligned 對最終打包的 apk進(jìn)行字節(jié)對齊。
注:Google Play 不接受未對齊過的 apk。
分析內(nèi)存使用情況
如果已經(jīng)獲得一個相對穩(wěn)定的版本,應(yīng)對程序整個生命周期的內(nèi)存使用狀況進(jìn)行分析。
使用多個進(jìn)程
如果程序需要執(zhí)行大量的后臺工作,可考慮將程序分為兩個進(jìn)程,一個進(jìn)程負(fù)責(zé) UI,另一個進(jìn)程負(fù)責(zé)后臺任務(wù)。比如音樂播放器。
代碼示例:
<serviceandroid:name=".PlaybackService"android:process=":background"/>
android:process屬性的值以“:”開頭,名稱可任意選取。
在決定是否使用多進(jìn)程前,應(yīng)注意,一個不執(zhí)行任何任務(wù)的空進(jìn)程至少也要占用 1.4 MB內(nèi)存。
另外要注意進(jìn)程的相互依賴性,比如如果將 ContentProvider 放在 UI 進(jìn)程中,而后臺任務(wù)進(jìn)程也需要調(diào)用 ContentProvider,就會導(dǎo)致 UI 進(jìn)程一直保留在 RAM 中。
參考文章
Android ListView工作原理完全解析,帶你從源碼的角度徹底理解,androidlistviewhttp://www.android100.org/html/201507/26/168809.htmlhttp://android.jobbole.com/81834/
MultiTypeDemo