Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android的性能優化

Android的性能優化

編輯:關於Android編程

一個Android程序不可能無限制的使用內存和CPU資源,過多的使用內存會導致程序內存溢出,也就是OOM。過多的使用CPU資源,做一些大量的耗時任務會導致手機卡頓或者無法響應,也就是ANR。性能優化的主要內容包括布局優化、繪制優化、內存洩漏優化、響應速度優化、ListView優化、Bitmap優化、線程優化。

 

布局優化

布局優化主要思想當然是減少布局文件的層級,盡可能少用ViewGroup,盡可能選用性能高的ViewGroup,比如能使用LinearLayout代替RelativeLayout就使用LinearLayout,因為RelativeLayout相對來說要復雜一些。當然能使用一個ViewGroup就使用一個,盡量少的去嵌套。

使用標簽、標簽和ViewStub。標簽主要用於布局的重用,相信都使用過。標簽一般和配合使用,減少布局的層級,ViewStub實現了按需加載,當需要的時候才進行加載,下面看看具體的使用。

標簽

可以把指定的布局加載到當前布局中,使用標簽可以提高復用性

 




    

 

在這裡用標簽添加了一個message布局,這樣就不需要再把布局重寫一遍。在標簽裡面只支持android:layout_開頭的屬性和id屬性,也就是說不能使用類似android:background這樣的屬性。還有一點需要注意,當標簽中使用了以android:layout_開頭的屬性,那麼必須去指定android:layout_height和android:layout_width,如上所示。

 

標簽

標簽一般和標簽一起使用從而減少布局的層級。所有Activity的根節點都是FrameLayout,如果我們在寫布局文件時,根節點也需要一個FrameLayout怎麼辦?如果直接再寫一個那不是兩個FrameLayout嵌套在一起,完全沒有必要,所以這時根節點寫成就Ok了。

又比如上面的標簽,如果include進來的布局裡面還是一個LinearLayout,外面標簽的根節點也是一個LinearLayout,那麼這時include布局的LinearLayout就可以使用標簽代替,如下所示:

 



ViewStub的使用

ViewStub繼承於View,寬高都為0,,本身不會參與任何的布局繪制過程,最佳用途就是實現View的延遲加載,避免資源浪費,在需要的時候才加載View,需要注意的是,加載view之後,viewstub本身就會被新加載進來的view替換掉。比如說一個加載失敗的界面,是沒有必要在初始化的時候就把它加載進來,這時通過ViewStub就可以做到在使用的時候再進行加載,提高了程序的性能。看看下面的例子:

 




    

加載進來的布局

 

 


final ViewStub viewStub = (ViewStub) findViewById(R.id.viewStub);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                TextView view = (TextView) viewStub.inflate();
                view.setText("加載進來的TextView");
            }
        }, 2000);
這裡延遲兩秒後調用ViewStub的inflate方法把ViewStub替換成要加載的布局,這時ViewStub就不存在了,因為被替換掉了,這個方法返回的正是加載進來的View,所以不需要采用findViewById的方法來找到它,我們加載進來的View是個TextView所以直接強轉為TextView就OK,除了inflate方法也可以采用setVisibility方法也可以。

 

 

繪制優化

繪制優化是指在onDraw方法中應該要避免執行大量的操作,主要體現在兩個方面:

首先,onDraw中不要創建新的局部變量,因為onDraw方法是會頻繁的被調用的,這樣每次調用後都會產生臨時對象,這樣不僅會占用過多的內存還會引起頻繁的GC。

另一方面的話,在onDraw中不應該做耗時的任務,也不能執行千萬次的循環操作,否則會造成繪制過程的不流暢,降低了用戶體驗。

 

內存洩漏優化

一個程序中,已經不再使用某個對象,但是因為仍然有引用指向它,垃圾回收器就無法回收它,當然該對象占用的內存就無法被使用,這就造成了內存洩露。下面看看幾種常見場景的內存洩漏

靜態變量引起的內存洩漏

 

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "lzy";
    private static Drawable sDrawable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);

        sDrawable = getDrawable(R.mipmap.ic_launcher);
        textView.setBackground(sDrawable);
        setContentView(textView);
    }
}
上述代碼中sDrawable是一個靜態變量,它持有一個textView的引用,而textView又持有這個Activity的引用,也就是說sDrawable持有了Activity的引用,如果銷毀這個Activity,由於sDrawable是一個靜態變量它不會被銷毀,也就說無法釋放Activity的引用,所以這個Activity也無法釋放,於是導致了內存洩漏。

 

 

單例模式引起的內存洩漏

因為單例的靜態特性使得單例的生命周期和應用的生命周期一樣長,這就說明了如果一個對象已經不需要使用了,而單例對象還持有該對象的引用,那麼這個對象將不能被正常回收,這就導致了內存洩漏。

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

如果Activity中使用這個單例,傳入的Context為Activity,那麼這個單例就持有了Activity的引用,將不會釋放,所以這個周期應該和Application的周期一樣,所以這裡AppManager的Context應該這樣傳,context.getApplicationContext(),這樣就可以得到全局的Context。

 

非靜態內部類創建靜態實例造成的內存洩漏

有的時候我們可能會在啟動頻繁的Activity中,為了避免重復創建相同的數據資源,會出現這種寫法:

 

public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }

    class TestResource {
        //...
    }
}
這樣就在Activity內部創建了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據,這樣雖然避免了資源的重復創建,不過這種寫法卻會造成內存洩漏,因為非靜態內部類默認會持有外部類的引用,而又使用了該非靜態內部類創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。正確的做法為:
將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請使用ApplicationContext 。

 

Handler造成的內存洩漏

Handler的使用造成的內存洩漏問題應該說最為常見了,平時在處理網絡任務或者封裝一些請求回調等api都應該會借助Handler來處理,對於Handler的使用代碼編寫一不規范即有可能造成內存洩漏,如下示例:

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

這種創建Handler的方式會造成內存洩漏,由於mHandler是Handler的非靜態匿名內部類的實例,所以它持有外部類Activity的引用,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那麼當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發內存洩漏,所以另外一種做法為:

 

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }

    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}
創建一個靜態Handler內部類,然後對Handler持有的對象使用弱引用,這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity洩漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息,使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable。當然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。

 

 

線程優化

線程優化的思想是采用線程池,避免程序中存在大量的Thread。線程池可以重用內部的線程,避免線程的創建的銷毀帶來的開銷,同時還能有效的控制線程池的最大並發數量。

 

一些優化建議

1.避免創建過多的對象。

2.不要過多使用枚舉,它占用的空間比整型大

3.常亮使用static final修飾
 

避免資源未關閉造成的內存洩漏

當使用了BraodcastReceiver、Cursor、Bitmap等資源時,當不需要使用時,需要及時釋放掉,若沒有釋放,則會引起內存洩漏。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved