年假即將結束,這篇文章也算是我自己梳理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);
}
static Context context;
public static void getTesLeak(Context context) {
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的效能優化需要了解的方面還有很多比如電量的優化、包大小、啟動速度的優化等等,上面列出的只是一部分常見的問題和解決辦法。在開發過程中需要優化的放要遠比上面寫道的多,還需要我們自己多積累經驗和結合實際考慮來優化。