android常見的效能優化方面的總結
年假即將結束,這篇文章也算是我自己梳理android知識的最後幾篇了。文章中的整體思路是根據《android開發藝術》結合平時開發經驗以及網上的資料完成的。內容用的原始碼都可以在GitHub上的專案中檢視到,希望閱讀完這篇文章能讓你有所收穫。
專案原始碼
目錄
- 佈局優化
- 繪製優化
- 記憶體洩漏優化
- ListView和Bitmap優化
佈局優化
- 減少佈局檔案的層級
- 刪除佈局中無用的控制元件和佈局
-
儘量使用簡單高效的ViewGroup,比如
FrameLayout
和LinaerLayout
- 可以使用include標籤複用佈局,使用merge標籤減少層級
include、merge標籤案例
在layout檔案中建立layout/incloude_merge_memory.xml
檔案內容如下:
<merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/mTV_incloud_merge" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/holo_red_light" android:gravity="center" android:padding="5dp" android:text="這是一個include的merge" /> </merge> 複製程式碼
在Activity的layout佈局引入:
<include layout="@layout/incloude_merge_memory" /> 複製程式碼
ViewStub
- 它是一個輕量級的佈局寬度、高度只有0,不參與繪製過程。
- 按需載入,不佔用空間。
- 當顯示ViewStub中的佈局時候,ViewStub會被替換掉,並且會被從佈局中移除。
xml程式碼:
<ViewStub android:id="@+id/mVS_layoutMemory" android:layout_width="match_parent" android:layout_height="80dp" android:background="@android:color/holo_blue_bright" android:inflatedId="@id/mRL_viewStubMemory" android:layout="@layout/viewsutub_memory" android:padding="10dp" /> 複製程式碼
上面程式碼中id為mVS_layoutMemory為ViewStub的id,而inflatedId是引入佈局@layout/viewsutub_memory
跟佈局的id。需要注意的是ViewStub中layout佈局是不支援merge標籤的,接下來看一下java程式碼的呼叫:
mVS_layoutMemory = findViewById(R.id.mVS_layoutMemory); mVS_layoutMemory.setVisibility(View.VISIBLE); 複製程式碼
繪製優化
- 不要在onDraw中建立新的佈局物件
- 不要在onDraw中做大量的耗時操作
記憶體洩漏優化
- 靜態變數引起的洩漏
- 單例模式引起的洩漏
- 非靜態內部類持有外部引用導致的洩漏
- Handler引起的記憶體洩漏
- 屬性動畫引起的洩漏
靜態變數導致的記憶體洩漏
這種情況常見的是Context的使用,比如我們寫了一個工具類,裡面的靜態方法需要用到Context。如果我們將Activity的this傳給這個方法,那麼Activity在被回收的時候由於這個靜態變數持有Activity的引用,導致不能被回收從而引起記憶體洩漏。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layoyt_memory); AppUtil.getTesLeak(this); } public static void getTesLeak(Context context) { Toast.makeText(context, "您的記憶體洩漏啦", Toast.LENGTH_SHORT).show(); } 複製程式碼
解決上面的問題也很多簡單,如果我們的工具類不是一定需要Activity的Context,難麼我們可以考慮使用getApplicationContext()
。因為getApplicationContext()
是和我們App的生命週期一樣長,如果App不退出他就不會被回收。
單例模式導致的記憶體洩漏
單例引起的記憶體洩漏,大概思路上面差不多,也是因為靜態變數的生命週期太長,如果程式不退出,系統就不會對其回收,這將導致本應該不用的物件不能回收,我們可以指定Context為getApplicationContext();來避免記憶體洩漏。
public class MemorySingle { //如果傳入上下文 private static Context context; private MemorySingle() { } public static MemorySingle getInstance(Context context) { //防止記憶體洩漏 MemorySingle.context = context.getApplicationContext(); return Menory.single; } static class Menory { private static final MemorySingle single = new MemorySingle(); } } 複製程式碼
非靜態內部類持有外部引用引起的洩漏
因為非靜態內部類的生命週期是和外部類的生命週期繫結在一起的,非靜態內部類會持有外部類的引用,如果我們在內部類中做一些耗時操作,如下面內部類sleepThread()方法讓執行緒睡10秒,在這個時候如果Activy要銷燬,但是因為內部類持有外部類的引用,它的sleepThread()方法還沒執行完,所以導致Activy不能被回收,引起記憶體洩漏。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layoyt_memory); TestLeak testLeak = new TestLeak(); testLeak.sleepThread(); } class TestLeak { private void sleepThread() { new Thread(new Runnable() { @Override public void run() { try { //睡10秒 Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } 複製程式碼
解決方法將TestLeak改成靜態內部類
static class TestLeak { private void sleepThread() { new Thread(new Runnable() { @Override public void run() { try { //睡10秒 Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } 複製程式碼
Handler引起的記憶體洩漏
我們使用Handler做訊息處理的時候可能不注意會用下面這種寫法:
private Handler mHanlder = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 200: mTV_incloud_merge.setText((String) msg.obj); break; } super.handleMessage(msg); } }; 複製程式碼
上面的mHanlder是Handler的非靜態匿名內部類,上面我們提到過非靜態匿名內部類會持有外部引用,所以如果使用上面的寫法也會引起記憶體洩漏。下面有兩種方式可以避免洩漏。
第一種方式: 使用靜態內部類+弱引用方式
static class MyHanlder extends Handler { //弱引用 WeakReference<Activity> mWeakRef; public MyHanlder(Activity activity) { mWeakRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 200: LayoutMemoryActivity activity = (LayoutMemoryActivity) mWeakRef.get(); activity.mTV_incloud_merge.setText((String) msg.obj); break; } } } 複製程式碼
第二種: Handler.Callback方式處理訊息
Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what) { case 200: mTV_incloud_merge.setText((String) msg.obj); break; } return false; } }) 複製程式碼
屬性動畫導致的記憶體洩漏
當一個Activy中有一個無限迴圈的屬性動畫,在Activy銷燬的時候沒有停止動畫也會引起記憶體洩漏
ObjectAnimator oA = ObjectAnimator.ofFloat(mBnt_layoutMemory, "rotation", 0, 360).setDuration(20000); oA.start(); 複製程式碼
上面的是一個按鈕選裝動畫,20秒後執行完,如果在動畫還為執行完的時候銷燬Activy,將會導致Activy無法釋放引起記憶體洩漏。下面是解決辦法
@Override protected void onDestroy() { super.onDestroy(); //取消動畫 oAnimator.cancel(); } 複製程式碼
ListView和Bitmap優化
ListView優化
ListView的優化是一個很長見的問題,主要是通過ViewHolder實現對item的複用,這裡不做過多的解釋了。在這我推薦一篇文章感興趣的可以看看,下面有一個例子:
@Override public View getView(final int position, View convertView,ViewGroup parent) { MyHolder myHolder = null; //判斷是否有快取佈局 if (convertView == null) { convertView =LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, null); myHolder = new MyHolder(convertView); convertView.setTag(position); } else { //得到快取佈局 myHolder = (MyHolder) convertView.getTag(); } myHolder.mTV_test1.setText(textContent); } class MyHolder { TextView mTV_test1; MyHolder(View view) { mTV_test1 = view.findViewById(android.R.id.text1); } } 複製程式碼
Bitmap優化
我們大部分圖片處理是使用glide
、'picasso',這些框架在圖片載入速度和效能優化方面已經很好了,但有些特殊情況可能需要我們自己實現圖片的處理,主要注意下面幾個方面。
- 對圖片進行壓縮
- 快取策略
- 圖片不使用的時候要記得釋放
總結
android的效能優化需要了解的方面還有很多比如電量的優化、包大小、啟動速度的優化等等,上面列出的只是一部分常見的問題和解決辦法。在開發過程中需要優化的放要遠比上面寫道的多,還需要我們自己多積累經驗和結合實際考慮來優化。