本篇文章已授權(quán)微信公眾號 鴻洋 發(fā)布
為什么要做性能優(yōu)化?
- 手機性能越來越好,不用糾結(jié)這些細微的性能?
Android每一個應(yīng)用都是運行的獨立的Dalivk虛擬機,根據(jù)不同的手機分配的可用內(nèi)存可能只有(32M、64M等),所謂的4GB、6GB運行內(nèi)存其實對于我們的應(yīng)用不是可以任意索取
優(yōu)秀的算法與效率低下的算法之間的運行效率要遠遠超過計算機硬件的的發(fā)展,雖然手機單核、雙核到4核、8核的發(fā)展,但性能優(yōu)化任然不可忽略
- 手機應(yīng)用一般使用的周期比較短,用完就關(guān)了。不像服務(wù)器應(yīng)用要長年累月運行,似乎影響不大?
- 現(xiàn)在一般的用戶都不會重啟手機,可能一個月都不會重啟。像微信這樣的APP,每天都在使用。如果一旦發(fā)生內(nèi)存泄漏,那么可能一點一點的累積,程序就會出現(xiàn)OOM。
- 等應(yīng)用出現(xiàn)卡頓、發(fā)燙等,再來關(guān)注性能優(yōu)化?
- 似乎是沒錯的。現(xiàn)在一般我們也都是等出現(xiàn)問題了再來找原因。但是學(xué)好性能優(yōu)化的目的不僅僅如此,我們在編碼階段就應(yīng)該從源頭來杜絕一些坑,這樣的成本比后期再來尋找原因要少得多
所以為了我們的應(yīng)用的健壯性、有良好的用戶體驗。性能優(yōu)化技術(shù),需要我們用心去研究和應(yīng)用。
什么是內(nèi)存泄漏?
JVM內(nèi)存管理
Java采用GC進行內(nèi)存管理。深入的JVM內(nèi)存管理知識,推薦《深入理解Java虛擬機》。關(guān)于內(nèi)存泄漏我們要知道,JVM內(nèi)存分配的幾種策略。
-
靜態(tài)的
靜態(tài)的存儲區(qū),內(nèi)存在程序編譯的時候就已經(jīng)分配好了,這塊內(nèi)存在程序整個運行期間都一直存在,它主要存放靜態(tài)數(shù)據(jù)、全局的static數(shù)據(jù)和一些常量。
-
棧式的
在執(zhí)行方法時,方法一些內(nèi)部變量的存儲都可以放在棧上面創(chuàng)建,方法執(zhí)行結(jié)束的時候這些存儲單元就會自動被注釋掉。棧 內(nèi)存包括分配的運算速度很快,因為內(nèi)在在處理器里面。當然容量有限,并且棧式一塊連續(xù)的內(nèi)存區(qū)域,大小是由操作系統(tǒng)決定的,他先進后 出,進出完成不會產(chǎn)生碎片,運行效率高且穩(wěn)定
-
堆式的
也叫動態(tài)內(nèi)存 。我們通常使用new 來申請分配一個內(nèi)存。這里也是我們討論內(nèi)存泄漏優(yōu)化的關(guān)鍵存儲區(qū)。GC會根據(jù)內(nèi)存的使用情況,對堆內(nèi)存里的垃圾內(nèi)存進行回收。堆內(nèi)存是一塊不連續(xù)的內(nèi)存區(qū)域,如果頻繁地new/remove會造成大量的內(nèi)存碎片,GC頻繁的回收,導(dǎo)致內(nèi)存抖動,這也會消耗我們應(yīng)用的性能
我們知道可以調(diào)用 System.gc();進行內(nèi)存回收,但是GC不一定會執(zhí)行。面對GC的機制,我們是否無能為力?其實我們可以通過聲明一些引用標記來讓GC更好對內(nèi)存進行回收。
類型 | 回收時機 | 生命周期 |
---|---|---|
StrongReference (強引用) | 任何時候GC是不能回收他的,哪怕內(nèi)存不足時,系統(tǒng)會直接拋出異常OutOfMemoryError,也不會去回收 | 進程終止 |
SoftReference (軟引用) | 當內(nèi)存足夠時不會回收這種引用類型的對象,只有當內(nèi)存不夠用時才會回收 | 內(nèi)存不足,進行GC的時候 |
WeakReference (弱引用) | GC一運行就會把給回收了 | GC后終止 |
PhantomReference (虛引用) | 如果一個對象與虛引用關(guān)聯(lián),則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收 | 任何時候都有可能 |
開發(fā)時,為了防止內(nèi)存溢出,處理一些比較占用內(nèi)存并且生命周期長的對象時,可以盡量使用軟引用和弱引用。
Tip
成員變量全部存儲在堆中(包括基本數(shù)據(jù)類型,引用及引用的對象實體),因為他們屬于類,類對象最終還是要被new出來的
局部變量的基本數(shù)據(jù)類型和引用存在棧中,應(yīng)用的對象實體存儲在堆中。因為它們屬于方法當中的變量,生命周期會隨著方法一起結(jié)束
內(nèi)存泄漏的定義
當一個對象已經(jīng)不需要使用了,本該被回收時,而有另外一個正在使用的對象持有它的引用,從而導(dǎo)致了對象不能被GC回收。這種導(dǎo)致了本該被回收的對象不能被回收而停留在堆內(nèi)存中,就產(chǎn)生了內(nèi)存泄漏
內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
內(nèi)存泄漏(Memory Leak)
進程中某些對象已經(jīng)沒有使用的價值了,但是他們卻還可以直接或間接地被引用到GC Root導(dǎo)致無法回收。當內(nèi)存泄漏過多的時候,再加上應(yīng)用本身占用的內(nèi)存,日積月累最終就會導(dǎo)致內(nèi)存溢出OOM內(nèi)存溢出(OOM)
當 應(yīng)用的heap資源超過了Dalvik虛擬機分配的內(nèi)存就會內(nèi)存溢出
內(nèi)存泄漏帶來的影響
應(yīng)用卡頓
泄漏的內(nèi)存影響了GC的內(nèi)存分配,過多的內(nèi)存泄漏會影響應(yīng)用的執(zhí)行效率應(yīng)用異常(OOM)
過多的內(nèi)存泄漏,最終會導(dǎo)致 Dalvik分配的內(nèi)存,出現(xiàn)OOM
Android開發(fā)常見的內(nèi)存泄漏
單例造成的內(nèi)存泄漏
- 錯誤示例
當調(diào)用getInstance時,如果傳入的context是Activity的context。只要這個單例沒有被釋放,那么這個
Activity也不會被釋放一直到進程退出才會釋放。
public class CommUtil {
private static CommUtil instance;
private Context context;
private CommUtil(Context context){
this.context = context;
}
public static CommUtil getInstance(Context mcontext){
if(instance == null){
instance = new CommUtil(mcontext);
}
return instance;
}
-
解決方案
能使用Application的Context就不要使用Activity的Content,Application的生命周期伴隨著整個進程的周期
非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏
- 錯誤示例
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 {
}
- 解決方案
將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類。(靜態(tài)內(nèi)部類不會隱式持有外部類)
Handler造成的內(nèi)存泄漏
- 錯誤示例
mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實例,所以它持有外部類Activity的引用,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那么當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏。
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> 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() {
Message message = Message.obtain();
mHandler.sendMessage(message);
}
- 解決方案
創(chuàng)建一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象使用弱引用,這樣在回收時也可以回收Handler持有的對象,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中還是可能會有待處理的消息,所以我們在Activity的Destroy時或者Stop時應(yīng)該移除消息隊列中的消息
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> 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);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
線程造成的內(nèi)存泄漏
- 錯誤示例
異步任務(wù)和Runnable都是一個匿名內(nèi)部類,因此它們對當前Activity都有一個隱式引用。如果Activity在銷毀之前,任務(wù)還未完成, 那么將導(dǎo)致Activity的內(nèi)存資源無法回收,造成內(nèi)存泄漏
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
- 解決方案
使用 靜態(tài)內(nèi)部類,避免了Activity的內(nèi)存資源泄漏,當然在Activity銷毀時候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask::cancel(),避免任務(wù)在后臺執(zhí)行浪費資源
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
資源未關(guān)閉造成的內(nèi)存泄漏
- 錯誤示例
對于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷,否則這些資源將不會被回收,造成內(nèi)存泄漏
- 解決方案
在Activity銷毀時及時關(guān)閉或者注銷
使用了靜態(tài)的Activity和View
- 錯誤示例
static view;
void setStaticView() {
view = findViewById(R.id.sv_button);
}
View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticView();
nextActivity();
}
});
static Activity activity;
void setStaticActivity() {
activity = this;
}
View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticActivity();
nextActivity();
}
});
-
解決方案
應(yīng)該及時將靜態(tài)的應(yīng)用 置為null,而且一般不建議將View及Activity設(shè)置為靜態(tài)
注冊了系統(tǒng)的服務(wù),但onDestory未注銷
- 錯誤示例
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
- 解決方案
//不需要用的時候記得移除監(jiān)聽
sensorManager.unregisterListener(listener);
不需要用的監(jiān)聽未移除會發(fā)生內(nèi)存泄露
- 錯誤示例
//add監(jiān)聽,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//監(jiān)聽view的加載,view加載出來的時候,計算他的寬高等。
}
});
- 解決方案
//計算完后,一定要移除這個監(jiān)聽
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
Tip
tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對象,不用考慮內(nèi)存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監(jiān)聽,放到集合里面,需要考慮內(nèi)存泄漏