編輯:關於Android編程
一個Android程序不可能無限制的使用內存和CPU資源,過多的使用內存會導致程序內存溢出,也就是OOM。過多的使用CPU資源,做一些大量的耗時任務會導致手機卡頓或者無法響應,也就是ANR。性能優化的主要內容包括布局優化、繪制優化、內存洩漏優化、響應速度優化、ListView優化、Bitmap優化、線程優化。
布局優化
布局優化主要思想當然是減少布局文件的層級,盡可能少用ViewGroup,盡可能選用性能高的ViewGroup,比如能使用LinearLayout代替RelativeLayout就使用LinearLayout,因為RelativeLayout相對來說要復雜一些。當然能使用一個ViewGroup就使用一個,盡量少的去嵌套。
使用
在這裡用
又比如上面的
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的內存資源不能正常回收。正確的做法為:
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創建一個靜態Handler內部類,然後對Handler持有的對象使用弱引用,這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity洩漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息,使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中所有消息和所有的Runnable。當然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。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); } }
線程優化
線程優化的思想是采用線程池,避免程序中存在大量的Thread。線程池可以重用內部的線程,避免線程的創建的銷毀帶來的開銷,同時還能有效的控制線程池的最大並發數量。
一些優化建議
1.避免創建過多的對象。
2.不要過多使用枚舉,它占用的空間比整型大
3.常亮使用static final修飾
當使用了BraodcastReceiver、Cursor、Bitmap等資源時,當不需要使用時,需要及時釋放掉,若沒有釋放,則會引起內存洩漏。
性能問題一般歸結為3類:1.UI卡頓和穩定性,這類問題用戶可直接感知,最為重要。2.內存問題,內存問題主要表現為內存洩漏。如果存在內存洩漏,應用會不斷消耗內存,容易導致頻
最近又有大片上映了,前幾天剛看完《末日崩塌》,《侏羅紀世界》又來了,對於大片迷來說是一種福利,所以這幾天手機上裝了各種電影票團購軟件,沒辦法,同樣的電影同樣的電影院同樣的
GridViewWithHeaderAndFooter控件,可以像使用ListView一樣為GridView添加Header和Footer。項目地址:https://gi
WeTest導讀內存是游戲的硬傷,如果沒有做好內存的管理問題,游戲極有可能會出現卡頓,閃退等影響用戶體驗的現象。本文介紹了在騰訊游戲在Unity游戲開發過程中常見的Mon