本文的內容均來自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中並不支持,我們可以通過反射機制實現:
- private static void initCompatibility() {
- try {
- mDebug_dumpHprofData = Debug.class.getMethod(
- "dumpHprofData", new Class[] { String.class } );
- /* success, this is a newer device */
- } catch (NoSuchMethodException nsme) {
- /* failure, must be older device */
- }
- }
- private static void dumpHprofData(String fileName) throws IOException {
- try {
- mDebug_dumpHprofData.invoke(null, fileName);
- } catch (InvocationTargetException ite) {
- /* unpack original exception when possible */
- Throwable cause = ite.getCause();
- if (cause instanceof IOException) {
- throw (IOException) cause;
- } else if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- } else if (cause instanceof Error) {
- throw (Error) cause;
- } else {
- /* unexpected checked exception; wrap and re-throw */
- throw new RuntimeException(ite);
- }
- } catch (IllegalAccessException ie) {
- System.err.println("unexpected " + ie);
- }
- }
3、判斷Intent服務是否存在
Android爲我們提供了非常多的Intent,也有第三方提供了很多Intent供我們使用,但由於版本、用戶是否安裝等原因,我們不清楚該Intent是否在本機上能否使用,爲此,就要進行判斷。代碼如下:
- public static boolean isIntentAvailable(Context context, String action) {
- final PackageManager packageManager = context.getPackageManager();
- final Intent intent = new Intent(action);
- List<ResolveInfo> list =
- packageManager.queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- return list.size() > 0;
- }
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構成,當該書是我們喜歡的書籍時,就顯示該星形,反之,就使這個星形半透明。這個實現的大致代碼如下:
- Book book = ...;
- TextView listItem = ...;
- listItem.setText(book.getTitle());
- Drawable star = context.getResources().getDrawable(R.drawable.star);
- if (book.isFavorite()) {
- star.setAlpha(255); // opaque
- } else {
- star.setAlpha(70); // translucent
- }
不幸的是,我們發現這個代碼無效,所有的星形Drawable都是一樣的。如下面左圖:
如果我們使用了一個新的Drawable,但是因爲BookItem的Drawable都引用到了同一個Constant state,因此我們嘗試對Constatn state進行修改的時候,引用該Constant state的Drawable也會相應的改變。
Android 1.5以上版本提供瞭解決的辦法:使用mutate()方法,使用該方法時,Drawable的Constant state會被複制,從而不會影響其他的Drawable,使用方式如下:
- Drawable star = context.getResources().getDrawable(R.drawable.star);
- if (book.isFavorite()) {
- star.mutate().setAlpha(255); // opaque
- } else {
- star. mutate().setAlpha(70); // translucent
- }
調用後效果如下圖:
5、更快的屏幕方向變換
在手機屏幕方向變換的時候,會銷燬當前Activity,並重新Create,此時Activity的所有數據都必須重新獲取。那當這些數據是比較大的數據的時候,如比較大的圖片、聲音、視頻,重新獲取的方式是一種非常不明智的做法。我們可以通過設置configChanges把這個屏蔽掉,但這種做法並不推薦,具體原因請參考後面的擴展閱讀。因此,爲了解決這個問題,我們用到了一個方法: onRetainNonConfigurationInstance(),
該方法允許你在屏幕變換的時候進行數據的傳遞,你可以在這裏傳輸所需要的數據,然後在onCreate裏調用getLastNonConfigurationInstance()獲取該數據。簡要代碼如下:
- @Override
- public Object onRetainNonConfigurationInstance() {
- final LoadedPhoto[] list = new LoadedPhoto[numberOfPhotos];
- keepPhotos(list);
- return list;
- }
onCreate下加載數據:
- private void loadPhotos() {
- final Object data = getLastNonConfigurationInstance();
- // The activity is starting for the first time, load the photos from Flickr
- if (data == null) {
- mTask = new GetPhotoListTask().execute(mCurrentPage);
- } else {
- // The activity was destroyed/created automatically, populate the grid
- // of photos with the images loaded by the previous activity
- final LoadedPhoto[] photos = (LoadedPhoto[]) data;
- for (LoadedPhoto photo : photos) {
- addPhoto(photo);
- }
- }
- }
擴展閱讀:https://devmaze.wordpress.com/2011/07/18/activities-and-background-threads-part-1/(可能需翻牆)
6、Layout 小技巧
- 創建可複用的佈局
Android爲我們提供了佈局的重複利用,如<requestFocus/>, <merge/>, <include/>,本文只介紹<include/>,<merge>會在稍後介紹。<include/>就如它的名字所提示的,它用於包含另外一個XML佈局,如:
- <com.android.launcher.Workspace
- android:id="@+id/workspace"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- launcher:defaultScreen="1">
- <include android:id="@+id/cell1" layout="@layout/workspace_screen" />
- <include android:id="@+id/cell2" layout="@layout/workspace_screen" />
- <include android:id="@+id/cell3" layout="@layout/workspace_screen" />
- </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兩次。以下以一個實例來說明,我們要實現這樣的一種效果,左邊有一個圖標,而標題在上方,下面是說明文字,如下面左圖,簡單的佈局概括如下圖右圖。
![]()
該佈局的源碼如下:
當我們使用該佈局作爲ListView的list item的layout時,就顯得很浪費,我們可以通過RelativeLayout實現相同的效果,但更高效:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:padding="6dip">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_marginRight="6dip"
- android:src="@drawable/icon" />
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="fill_parent">
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:gravity="center_vertical"
- android:text="My Application" />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:text="Simple application that shows how to use RelativeLayout" />
- </LinearLayout>
- </LinearLayout>
爲什麼使用RelativeLayout實現佈局比LinearLayout更高效呢,我們可以通過 HierarchyViewer去查看:
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="?android:attr/listPreferredItemHeight"
- android:padding="6dip">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_alignParentTop="true"
- android:layout_alignParentBottom="true"
- android:layout_marginRight="6dip"
- android:src="@drawable/icon" />
- <TextView
- android:id="@+id/secondLine"
- android:layout_width="fill_parent"
- android:layout_height="26dip"
- android:layout_toRightOf="@id/icon"
- android:layout_alignParentBottom="true"
- android:layout_alignParentRight="true"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:text="Simple application that shows how to use RelativeLayout" />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/icon"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:layout_above="@id/secondLine"
- android:layout_alignWithParentIfMissing="true"
- android:gravity="center_vertical"
- android:text="My Application" />
- </RelativeLayout>
如上圖所示,用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的屬性,做下簡單介紹:
- <ViewStub
- android:id="@+id/stub_import"
- android:inflatedId="@+id/panel_import"
- android:layout="@layout/progress_overlay"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- 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數量。說的不容易理解,直接講實例吧。我們用下面左邊的佈局實現其下方的效果:
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <ImageView
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scaleType="center"
- android:src="@drawable/golden_gate" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="20dip"
- android:layout_gravity="center_horizontal|bottom"
- android:padding="12dip"
- android:background="#AA000000"
- android:textColor="#ffffffff"
- android:text="Golden Gate" />
- </FrameLayout>
我們使用 HierarchyViewer觀察裏面佈局情況,就能發現問題了:
我們可以發現,在這個View Tree裏,有兩個FrameLayout,我們的FrameLayout都是使用fill_parent,這明顯就造成了浪費。系統多初始化了一個FrameLayout了。
這就是<merge/>派上用場的時候了,當LayoutInflater遇到<merge/>標籤的時候,它會略過它,直接添加它的children到parent上。舉個例子,我們用<merge/>代替了上面的<FrameLayout>:
- <merge xmlns:android="http://schemas.android.com/apk/res/android">
- <ImageView
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scaleType="center"
- android:src="@drawable/golden_gate" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="20dip"
- android:layout_gravity="center_horizontal|bottom"
- android:padding="12dip"
- android:background="#AA000000"
- android:textColor="#ffffffff"
- android:text="Golden Gate" />
- </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去實現:
- <resources>
- <style name="Theme.NoBackground" parent="android:Theme">
- <item name="android:windowBackground">@null</item>
- </style>
- </resources>
原文地址:http://blog.csdn.net/billpig/article/details/6672588