一個良好的App是經過嚴格的性能優化和記憶體優化給使用者帶來良好的操作。今天就說一下記憶體優化。
Java四種引用
Java的四種引用方式。
- 強引用 無論記憶體充足與否,系統都不會銷毀對象執行個體。
- 弱引用 隻要産生了GC(垃圾回收器),弱引用執行個體對象容易被銷毀。
- 虛引用 檢測對象是否已經回收
- 軟引用 隻要記憶體不足,就會被釋放
通過代碼來示範一下效果。
public static void main(String[] args){
//強引用 記憶體無論怎麼樣系統都不會釋放
String str=new String("String");
//軟引用 隻要記憶體不足就釋放掉
SoftReference<String> softReference=new SoftReference<String>(str);
//弱引用 隻要系統産生了GC(垃圾回收)它引用的對象就會被釋放掉
WeakReference<String> weakReference=new WeakReference<String>(str);
//虛引用 判斷對象是否已被回收 很少用得上
PhantomReference phantomReference=new PhantomReference<String>(str)
System.out.println("強引用"+str);
System.out.println("軟引用"+softReference.get());
System.out.println("弱引用"+weakReference.get());
}
當點選運作時候,輸出的結果。
強引用String
軟引用String
弱引用String
那麼如何手動去銷毀其對象執行個體呢?
強引用可以手動将對象置為null。
弱引用和軟引用可以手動調用clear()的方法,弱引用也可以調用gc進行回收,代碼如下。
public static void main(String[] args){
//強引用 記憶體無論怎麼樣系統都不會釋放
String str=new String("String");
//軟引用 記憶體不足就釋放掉
SoftReference<String> softReference=new SoftReference<String>(str);
//弱引用 隻要系統産生了GC(垃圾回收)它引用的對象就會被釋放掉
WeakReference<String> weakReference=new WeakReference<String>(str);
//虛引用 判斷對象是否已被回收
PhantomReference phantomReference=new PhantomReference<String>(str)
str=null;
softReference.clear();
System.out.println("強引用"+str);
System.out.println("軟引用"+softReference.get());
System.gc();
//這種方式也是可以的
weakReference.clear();
System.out.println("弱引用"+weakReference.get());
}
運作輸出效果
強引用null
軟引用null
弱引用null
什麼是記憶體洩漏?
舉一個例子,如果一個Activity啟動的時候執行了長時間的耗時操作,當該耗時操作并未完全結束時,使用者點選了back回退,此時的系統産生的Activity頁面消失,背景耗時操作仍然運作并持有Activity的對象執行個體,這樣就會導緻記憶體洩漏。也就是說,無用對象仍然被引用而導緻記憶體洩漏。
為了提高開發的穩定性,使用相關的工具進行查找記憶體洩漏的原因。
LeakCannary
這是第三方工具,可以友善得運用到項目中并快速的找到app是否存在記憶體洩漏的隐患。
1.首先是添加依賴
dependencies {}debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5' testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
2.建立一個類繼承自Application,在onCreate()方法中添加如下代碼:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
這樣就能使用了。貼代碼。
MainActivity類
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
MyThread myThread = new MyThread();
myThread.start();
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = ; i < ; i++) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
MyApplication類
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
添加一個按鈕
<Button
android:id="@+id/btn_press"
android:text="Press"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
運作效果。當點選按鈕是按back回退到主界面。過一會在導航欄的位置就會出現相應的洩漏資訊。
常見的記憶體洩漏
内部類隐式持有外部類的引用導緻的記憶體洩漏。
好比上面示範的例子。當Activity建立的執行個體被系統銷毀時,建立的MyThread類依舊持有activity的對象是以報錯。
解決的辦法有三種,分别是在
- 将MyThread類變成靜态類,
- 在包下重新建立一個MyThread類。然後在MainActivity調用就行了。
- 采用弱引用的方式
這裡示範一下弱引用的方式防止記憶體洩漏。貼上代碼。修改上面的代碼。
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
MyThread myThread = new MyThread(MainActivity.this);
myThread.start();
}
public static class MyThread extends Thread{
//建立一個弱引用對象
private WeakReference<MainActivity> mReference=null;
//在構造函數初始化弱引用的對象
public MyThread(MainActivity activity){
this.mReference=new WeakReference<MainActivity>(activity);
}
@Override
public void run() {
//取得弱引用的對象并和activity關聯
MainActivity activity = mReference.get();
for (int i = ; i < ; i++) {
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
這樣工具就不會提示出錯了。
Handler記憶體洩漏
Handler經常存在記憶體溢出的隐患,因為Handler發送消息過程中,可能存在長時間的耗時操作。為了避免這種情況,我們同樣适用弱引用來防止記憶體洩漏。
代碼示範一下,
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
private Handler mhandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case :
btn_press.setText("接收消息,修改文本");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
StartHandler();
}
private void StartHandler() {
Message message=Message.obtain();
message.what=;
message.obj="發送延遲任務";
mhandler.sendMessageDelayed(message,);
}
}
雖然按鈕的文本确實改變了,但是工具依舊會報錯,這是因為,Handler中的Looper類,最後需要從MessageQueue中取出消息,如果我們在發送消息的工程共需要20s的延遲時間,那麼Looper這個類就一直處于等待的狀态是以導緻了記憶體洩漏。
同樣,解決的辦法是使用弱引用的方法。
貼代碼
public class MainActivity extends Activity implements View.OnClickListener {
private Button btn_press;
//将MainActivity 傳進去
private MyHandler myHandler=new MyHandler(MainActivity.this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_press= (Button) findViewById(R.id.btn_press);
btn_press.setOnClickListener(this);
}
@Override
public void onClick(View view) {
StartHandler();
}
private void StartHandler() {
Message message=Message.obtain();
message.what=;
message.obj="發送延遲任務";
mhandler.sendMessageDelayed(message,);
}
private static class MyHandler extends Handler{
private WeakReference<MainActivity> mReference;
public MyHandler(MainActivity activity){
this.mReference=new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = mReference.get();
if(mainActivity==null){
return ;
}
switch (msg.what){
case :
//通過軟引用中的對象可以拿到外部非靜态的Button對象
mainActivity.btn_press.setText("修改了按鈕文本");
break;
}
}
}
}
Fragment如何檢測記憶體洩漏?
對于Fragment的記憶體洩漏,它有特殊的方法。
需要在MyApplication建立一個RefWatcher觀察者對象。
public class MyApplication extends Application {
public static RefWatcher mRefWatcher;
@Override public void onCreate() {
super.onCreate();
//...
mRefWatcher = LeakCanary.install(this);
// Normal app init code...
}
}
在Fragment的onDestroy()方法中添加
同理方法案例和上面相同。讀者自己摸索。