天天看點

安卓學習筆記之記憶體優化(一)

一個良好的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()方法中添加

同理方法案例和上面相同。讀者自己摸索。