android 代碼優化

本文的內容均來自Android SDK文檔Resources下的Articles,本文對一些比較有用、常用的知識做一個簡要的總結。

1、避免內存泄露

Context經常用來加載訪問各種Resources,這就是爲什麼很多控件在創建的時候都要傳入一個Context的原因。我們的程序大多數會使用到兩種Context:Activity和Application,而且我們大多數使用前一個。

在一個Context-Activity的全局變量中不要保存Drawable、View、Context-Activity的引用,因爲Drawable和View各自保存Context的引用,如果在全局變量中保存,那麼當Activity銷燬重新創建後會造成內存泄露。如果要在上下文中保存全局變量,請用Context-Application。

中文參考:http://www.cnblogs.com/xirihanlin/archive/2010/04/09/1707986.html

2、向後兼容性

Android各個版本存在着差異,爲了編寫出兼容性比較強的程序,除了設置<uses-sdk android:minSdkVersion="3" />之外, 我們可以通過反射機制去實現這種兼容性,如,使用 android.os.Debug.dumpHprofData(String filename),該方法只在Android1.0中去實現,但在Android1.5中並不支持,我們可以通過反射機制實現:


  1. private static void initCompatibility() {  
  2.     try {  
  3.         mDebug_dumpHprofData = Debug.class.getMethod(  
  4.                 "dumpHprofData"new Class[] { String.class } );  
  5.         /* success, this is a newer device */  
  6.     } catch (NoSuchMethodException nsme) {  
  7.         /* failure, must be older device */  
  8.     }  
  9. }  
  10.   
  11. private static void dumpHprofData(String fileName) throws IOException {  
  12.     try {  
  13.         mDebug_dumpHprofData.invoke(null, fileName);  
  14.     } catch (InvocationTargetException ite) {  
  15.         /* unpack original exception when possible */  
  16.         Throwable cause = ite.getCause();  
  17.         if (cause instanceof IOException) {  
  18.             throw (IOException) cause;  
  19.         } else if (cause instanceof RuntimeException) {  
  20.             throw (RuntimeException) cause;  
  21.         } else if (cause instanceof Error) {  
  22.             throw (Error) cause;  
  23.         } else {  
  24.             /* unexpected checked exception; wrap and re-throw */  
  25.             throw new RuntimeException(ite);  
  26.         }  
  27.     } catch (IllegalAccessException ie) {  
  28.         System.err.println("unexpected " + ie);  
  29.     }  
  30. }  



3、判斷Intent服務是否存在

Android爲我們提供了非常多的Intent,也有第三方提供了很多Intent供我們使用,但由於版本、用戶是否安裝等原因,我們不清楚該Intent是否在本機上能否使用,爲此,就要進行判斷。代碼如下:

  1. public static boolean isIntentAvailable(Context context, String action) {  
  2.     final PackageManager packageManager = context.getPackageManager();  
  3.     final Intent intent = new Intent(action);  
  4.     List<ResolveInfo> list =  
  5.             packageManager.queryIntentActivities(intent,  
  6.                     PackageManager.MATCH_DEFAULT_ONLY);  
  7.     return list.size() > 0;  
  8. }  


4、Drawable Mutations

Drawable允許我們在不同的View之間共享資源,具體共享的實現機制是通過一個叫作"constant state"的方式去實現的,它保存了一個資源的所有的屬性,比如,在一個Button中,constant state保存了Button的bitmap,整個程序默認的Button都是共享使用這個bitmap,從而節省了內存空間。constant state簡單的理解如下圖:

constant state的方式避免了很多內存的浪費,但是在實際的應用過程中如:試圖修改Drawable的constate state,也遇到一些問題,比如,我們用一個ListView顯示書籍列表,每個Book Item由一個星形的Drawable和Text構成,當該書是我們喜歡的書籍時,就顯示該星形,反之,就使這個星形半透明。這個實現的大致代碼如下:

  1. Book book = ...;  
  2. TextView listItem = ...;  
  3.   
  4. listItem.setText(book.getTitle());  
  5.   
  6. Drawable star = context.getResources().getDrawable(R.drawable.star);  
  7. if (book.isFavorite()) {  
  8.   star.setAlpha(255); // opaque  
  9. else {  
  10.   star.setAlpha(70); // translucent  
  11. }  


不幸的是,我們發現這個代碼無效,所有的星形Drawable都是一樣的。如下面左圖:

     

如果我們使用了一個新的Drawable,但是因爲BookItem的Drawable都引用到了同一個Constant state,因此我們嘗試對Constatn state進行修改的時候,引用該Constant state的Drawable也會相應的改變。

Android 1.5以上版本提供瞭解決的辦法:使用mutate()方法,使用該方法時,Drawable的Constant state會被複制,從而不會影響其他的Drawable,使用方式如下:


  1. Drawable star = context.getResources().getDrawable(R.drawable.star);  
  2. if (book.isFavorite()) {  
  3.   star.mutate().setAlpha(255); // opaque  
  4. else {  
  5.   star. mutate().setAlpha(70); // translucent  
  6. }  

調用後效果如下圖:




5、更快的屏幕方向變換

在手機屏幕方向變換的時候,會銷燬當前Activity,並重新Create,此時Activity的所有數據都必須重新獲取。那當這些數據是比較大的數據的時候,如比較大的圖片、聲音、視頻,重新獲取的方式是一種非常不明智的做法。我們可以通過設置configChanges把這個屏蔽掉,但這種做法並不推薦,具體原因請參考後面的擴展閱讀。因此,爲了解決這個問題,我們用到了一個方法: onRetainNonConfigurationInstance()該方法允許你在屏幕變換的時候進行數據的傳遞,你可以在這裏傳輸所需要的數據,然後在onCreate裏調用getLastNonConfigurationInstance()獲取該數據。簡要代碼如下:

  1. @Override  
  2. public Object onRetainNonConfigurationInstance() {  
  3.     final LoadedPhoto[] list = new LoadedPhoto[numberOfPhotos];  
  4.     keepPhotos(list);  
  5.     return list;  
  6. }  

onCreate下加載數據:

  1. private void loadPhotos() {  
  2.     final Object data = getLastNonConfigurationInstance();  
  3.       
  4.     // The activity is starting for the first time, load the photos from Flickr  
  5.     if (data == null) {  
  6.         mTask = new GetPhotoListTask().execute(mCurrentPage);  
  7.     } else {  
  8.         // The activity was destroyed/created automatically, populate the grid  
  9.         // of photos with the images loaded by the previous activity  
  10.         final LoadedPhoto[] photos = (LoadedPhoto[]) data;  
  11.         for (LoadedPhoto photo : photos) {  
  12.             addPhoto(photo);  
  13.         }  
  14.     }  
  15. }  

擴展閱讀:https://devmaze.wordpress.com/2011/07/18/activities-and-background-threads-part-1/(可能需翻牆)


6、Layout 小技巧

  • 創建可複用的佈局
Android爲我們提供了佈局的重複利用,如<requestFocus/>, <merge/>, <include/>,本文只介紹<include/>,<merge>會在稍後介紹。<include/>就如它的名字所提示的,它用於包含另外一個XML佈局,如:
  1. <com.android.launcher.Workspace  
  2.     android:id="@+id/workspace"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent"  
  5.   
  6.     launcher:defaultScreen="1">  
  7.   
  8.     <include android:id="@+id/cell1" layout="@layout/workspace_screen" />  
  9.     <include android:id="@+id/cell2" layout="@layout/workspace_screen" />  
  10.     <include android:id="@+id/cell3" layout="@layout/workspace_screen" />  
  11.   
  12. </com.android.launcher.Workspace>  

在該佈局裏,include了3個layout,都是workspace_screen的layout,而且在<include/>標籤中,只有3個屬性:id, layout_width,layout_height,用於對原佈局的覆蓋。
  • 創建高效的佈局
我們在創建UI的時候經常沒有使用一個更有效的方式去實現UI。比較典型的例子就是LinearLayout的例子,它使用層成嵌套的方式去實現佈局,但是每個View或者Layout都需要初始化、繪製等,如果是很多的嵌套的會,將會降低系統性能。而且當我們在是使用LinearLayout的weight屬性時,都會使child view,重新measure兩次。以下以一個實例來說明,我們要實現這樣的一種效果,左邊有一個圖標,而標題在上方,下面是說明文字,如下面左圖,簡單的佈局概括如下圖右圖。
   

該佈局的源碼如下:

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="?android:attr/listPreferredItemHeight"  
  4.       
  5.     android:padding="6dip">  
  6.       
  7.     <ImageView  
  8.         android:id="@+id/icon"  
  9.           
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="fill_parent"  
  12.         android:layout_marginRight="6dip"  
  13.           
  14.         android:src="@drawable/icon" />  
  15.   
  16.     <LinearLayout  
  17.         android:orientation="vertical"  
  18.       
  19.         android:layout_width="0dip"  
  20.         android:layout_weight="1"  
  21.         android:layout_height="fill_parent">  
  22.   
  23.         <TextView  
  24.             android:layout_width="fill_parent"  
  25.             android:layout_height="0dip"  
  26.             android:layout_weight="1"  
  27.                       
  28.             android:gravity="center_vertical"  
  29.             android:text="My Application" />  
  30.               
  31.         <TextView    
  32.             android:layout_width="fill_parent"  
  33.             android:layout_height="0dip"  
  34.             android:layout_weight="1"   
  35.               
  36.             android:singleLine="true"  
  37.             android:ellipsize="marquee"  
  38.             android:text="Simple application that shows how to use RelativeLayout" />  
  39.               
  40.     </LinearLayout>  
  41.   
  42. </LinearLayout>  
當我們使用該佈局作爲ListView的list item的layout時,就顯得很浪費,我們可以通過RelativeLayout實現相同的效果,但更高效:
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="?android:attr/listPreferredItemHeight"  
  4.       
  5.     android:padding="6dip">  
  6.       
  7.     <ImageView  
  8.         android:id="@+id/icon"  
  9.           
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="fill_parent"  
  12.           
  13.         android:layout_alignParentTop="true"  
  14.         android:layout_alignParentBottom="true"  
  15.         android:layout_marginRight="6dip"  
  16.           
  17.         android:src="@drawable/icon" />  
  18.   
  19.     <TextView    
  20.         android:id="@+id/secondLine"  
  21.   
  22.         android:layout_width="fill_parent"  
  23.         android:layout_height="26dip"   
  24.           
  25.         android:layout_toRightOf="@id/icon"  
  26.         android:layout_alignParentBottom="true"  
  27.         android:layout_alignParentRight="true"  
  28.           
  29.         android:singleLine="true"  
  30.         android:ellipsize="marquee"  
  31.         android:text="Simple application that shows how to use RelativeLayout" />  
  32.   
  33.     <TextView  
  34.         android:layout_width="fill_parent"  
  35.         android:layout_height="wrap_content"  
  36.           
  37.         android:layout_toRightOf="@id/icon"  
  38.         android:layout_alignParentRight="true"  
  39.         android:layout_alignParentTop="true"  
  40.         android:layout_above="@id/secondLine"  
  41.         android:layout_alignWithParentIfMissing="true"  
  42.                   
  43.         android:gravity="center_vertical"  
  44.         android:text="My Application" />  
  45.   
  46. </RelativeLayout>  
爲什麼使用RelativeLayout實現佈局比LinearLayout更高效呢,我們可以通過 HierarchyViewer去查看:


如上圖所示,用RelativeLayout減少了View的個數,也就減少了系統初始化View的消耗。另外,消除了LinearLayout的weight所帶來的額外消耗。

但是在使用RelativeLayout的過程中,也會帶來一些不便,比如,我們要根據情況把說明文字隱藏起來,當我們使用RelativeLayout的時候,我們把說明文字放置在Parent的底部,把標題放置在說明文字的上方。但是當說明文字消失的時候,那標題就失去了相對的位置,那它要如何放置呢?我們可以通過設置 layout_alignWithParentIfMissing屬性去實現。

  • 使用ViewStubs
我們前面介紹了<include/>標籤去共享不同的佈局,現在介紹另外一種方式:ViewStubs,該方式與<include/>最大的區別就在於ViewStubs是在需要的時候才inflate,而<include/>在初始化的時候就inflate。利用ViewStubs的lazy include特點,我們可以把比較不常用的UI放在ViewStubs裏面。以Shelves 爲例,在進入主界面的界面以及導入書籍的界面如下:
  

上面右圖下面的按鈕就是使用ViewStubs實現的,整個佈局的前後比較如下:
初始化界面:

導入書籍界面:

我們,可以看到,只有在導入書籍的時候該ViewStubs才inflate,在那之前一直都沒inflate,這樣就節省了系統的資源。
關於ViewStub的屬性,做下簡單介紹:
  1. <ViewStub  
  2.   android:id="@+id/stub_import"  
  3.   android:inflatedId="@+id/panel_import"  
  4.   
  5.   android:layout="@layout/progress_overlay"  
  6.   
  7.   android:layout_width="fill_parent"  
  8.   android:layout_height="wrap_content"  
  9.   android:layout_gravity="bottom" />  

android:id是當前佈局下的viewStub的id,而inflatedId是做是用來覆蓋所include的root佈局的id,android:layout之來指定所include的layout。
另外,我們可以通過inflate()或者顯示設置ViewStubs爲VISIBLE或者INVISIBLE初始化該ViewStubs。
  • Merging Layouts

<merge/>是另外一種複用佈局的標籤,它提供了佈局的優化並且減少View tree的level數量。說的不容易理解,直接講實例吧。我們用下面左邊的佈局實現其下方的效果:

  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="fill_parent">  
  4.   
  5.     <ImageView    
  6.         android:layout_width="fill_parent"   
  7.         android:layout_height="fill_parent"   
  8.       
  9.         android:scaleType="center"  
  10.         android:src="@drawable/golden_gate" />  
  11.       
  12.     <TextView  
  13.         android:layout_width="wrap_content"   
  14.         android:layout_height="wrap_content"   
  15.         android:layout_marginBottom="20dip"  
  16.         android:layout_gravity="center_horizontal|bottom"  
  17.   
  18.         android:padding="12dip"  
  19.           
  20.         android:background="#AA000000"  
  21.         android:textColor="#ffffffff"  
  22.           
  23.         android:text="Golden Gate" />  
  24.   
  25. </FrameLayout>  


我們使用 HierarchyViewer觀察裏面佈局情況,就能發現問題了:

我們可以發現,在這個View Tree裏,有兩個FrameLayout,我們的FrameLayout都是使用fill_parent,這明顯就造成了浪費。系統多初始化了一個FrameLayout了。

這就是<merge/>派上用場的時候了,當LayoutInflater遇到<merge/>標籤的時候,它會略過它,直接添加它的children到parent上。舉個例子,我們用<merge/>代替了上面的<FrameLayout>:

  1. <merge xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <ImageView    
  4.         android:layout_width="fill_parent"   
  5.         android:layout_height="fill_parent"   
  6.       
  7.         android:scaleType="center"  
  8.         android:src="@drawable/golden_gate" />  
  9.       
  10.     <TextView  
  11.         android:layout_width="wrap_content"   
  12.         android:layout_height="wrap_content"   
  13.         android:layout_marginBottom="20dip"  
  14.         android:layout_gravity="center_horizontal|bottom"  
  15.   
  16.         android:padding="12dip"  
  17.           
  18.         android:background="#AA000000"  
  19.         android:textColor="#ffffffff"  
  20.           
  21.         android:text="Golden Gate" />  
  22.   
  23. </merge>  

我們可以發現,ImageView和TextView直接添加在上一級的FrameLayout裏了:



有幾點必須注意:

1、merge只能用在佈局文件的root標籤上

2、merge只能使用在root標籤爲FrameLayout的情況下

3、當我們inflate一個<merge/>標籤開始的佈局時,我們必須指定一個parent的ViewGroup,並且設置attachToRoot爲True(請參考文檔的 inflate(int, android.view.ViewGroup, boolean) 方法)


7、Painless Threading

非UI線程不能用於去修改UI,否則會報異常。非UI線程要修改UI,可使用如下方法:

或者也可以使用AsyncTask


8、Window Backgrounds & UI Speed

大多數的應用的Activity會在視覺上覆蓋掉Windows的Background,而在這之中就隱藏了其中的一個額外的資源消耗存在:系統必須得爲Windows的Background做初始化、渲染等。因此,我們可以把Windows的Background去掉,節約系統資源。效果比較爲如下兩圖,左圖爲未去掉效果前的fps,右圖爲去掉效果後的fps


   

具體如何去掉?我們可以通過設置自定義的Theme去實現:

  1. <resources>  
  2.     <style name="Theme.NoBackground" parent="android:Theme">  
  3.         <item name="android:windowBackground">@null</item>  
  4.     </style>  
  5. </resources> 

原文地址:http://blog.csdn.net/billpig/article/details/6672588

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章