Androidについて話す:メモリの最適化についてのこと〜

開発プロセスでは、LeakCanaryオープンソースフレームワークは、メモリリークの問題点を監視するために友人によって最も頻繁に使用されていると思います。これはより効率的であり、問​​題点をすばやく見つけて、対応する最適化処理を行うことができますが、 LeakCanaryは結局のところオープンソースフレームワークです。特別な理由により、潜在的なメモリリークを分析するためにLeakCanaryオープンソースフレームワークを使用できない場合があります。したがって、メモリリークのシナリオを検出するために、メモリ分析ツールをよく使用します。

メモリプロファイラーとMAT分析ツールを使用して、アプリケーションにメモリリークがあるかどうかを検出します。

最初のソースコード:

MemoryMonitorActivity:

public class MemoryMonitorActivity extends BaseActivity{
@BindView(R.id.btnMemoryThrashing)
Button btnMemoryThrashing;
@BindView(R.id.btnHanlder)
Button btnHanlder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.memory_monitor_activity);
    ButterKnife.bind(this);
}
@OnClick({R.id.btnMemoryThrashing,R.id.btnHanlder})
public void onViewClicked(View view) {
    switch (view.getId()){
        case R.id.btnMemoryThrashing:
            SingleManager.getInstance(this).get();
            break;
        case R.id.btnHanlder:
           sendMsg();
            break;
    }
}

private void sendMsg(){
    mHandler.postDelayed(runnable,5000);
}
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        Message message = new Message();
        message.what=1;
        message.obj = "子线程发消息";
        mHandler.sendMessage(message);
    }
};
}
复制代码

SingleManager:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
         switch (msg.what){
             case 1:
                 Toast.makeText(MemoryMonitorActivity.this,"主线程接收: "+msg.obj,Toast.LENGTH_LONG).show();
                 break;
         }
    }
};}SingleManager:public class SingleManager {

  private static SingleManager instance = null;

  private  Context context = null;

  private SingleManager(Context context){
      this.context = context;
  }
  public static SingleManager getInstance(Context context){
      if (instance==null){
          instance = new SingleManager(context);
      }
      return instance;
  }

  public void get(){
      LogUtils.e(" logcat context:  "+ context );
  }
  }
复制代码

ソースコードからわかるように、シングルトンモード、ハンドラー、および匿名内部クラスによって引き起こされるメモリリークの例では、今回はメモリリークの原因について説明せず、メモリプロファイラーツールを使用してそれらを分析します。

上の図からわかるように、MemoryMonitorActivityインターフェイスは終了または破棄されていますが、MemoryMonitorActivityインスタンスはまだ存在しており、そのインスタンスオブジェクト自体のサイズは0.69Kであり、参照サイズを含むMemoryMonitorActivityは142.7Kです。アクティビティの典型的なメモリリークであり、メモリの浪費があります。しかし、上記では、アクティビティのメモリリークの本当の原因を特定することはできません。次に、次の図を引き続き見てください。

上記から、次のことがわかります。

1.コンテキストは外部MemoryMonitorActivity参照を保持します。これにより、GCはリサイクルに失敗します。

2.ランナブルは外部のMemoryMonitorActivity参照を保持します。ソースコードから、ランナブルはMemoryMonitorActivityクラスへの外部参照を保持する内部クラスであることがわかります。これにより、GCはリサイクルに失敗します。

3.ハンドラーは外部MemoryMonitorActivity参照を保持します。これにより、GCはリサイクルに失敗します。

上記の3つのポイントで、アクティビティのメモリリークの本当の理由は、参照を保持し、GCがそれを時間内にリサイクルせず、メモリの浪費があることです。

那接下来怎么优化它呢,别着急。我们知道如何使用Memory Profiler来检测应用是否存在内存泄漏。接下来我们使用MAT来分析下内存占用情况。

我们点击Top Consumers看看,如下:

从上图可以看出,由于MemoryMonitorActivity界面已经销毁了,仍然存在很多实例,因此它已经存在内存泄露了。我们需要熟悉Memory Profiler和 MAT工具的使用,可以进行综合分析来定位内存泄露的真正原因。

接下来我们就需要优化代码了。

优化后源码如下:

MemoryMonitorActivity:

public class MemoryMonitorActivity extends BaseActivity{
@BindView(R.id.btnMemoryThrashing)
Button btnMemoryThrashing;
@BindView(R.id.btnHanlder)
Button btnHanlder;

private MyHandler myHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.memory_monitor_activity);
    ButterKnife.bind(this);
    myHandler = new MyHandler(this);
}
@OnClick({R.id.btnMemoryThrashing,R.id.btnHanlder})
public void onViewClicked(View view) {
    switch (view.getId()){
        case R.id.btnMemoryThrashing:
            SingleManager.getInstance(this).get();
            break;
        case R.id.btnHanlder:
            sendMsg();
            break;
    }
}

private void sendMsg(){
    myHandler.postDelayed(runnable,5000);
}

/**
 * 匿名内部类持有外部类,需要实例化一个Runnable对象,在onDestroy方法runnable设置null即可
 */
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        Message message = new Message();
        message.what=1;
        message.obj = "子线程发消息";
        myHandler.sendMessage(message);
    }
};

/**
 * 使用静态handler的弱引用方式来避免内存泄露
 */
private static class MyHandler extends Handler{
    private final WeakReference<MemoryMonitorActivity> memoryMonitorActivity;
    public MyHandler(MemoryMonitorActivity activity){
        memoryMonitorActivity = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        MemoryMonitorActivity activity = memoryMonitorActivity.get();
        super.handleMessage(msg);
        if (activity==null)return;
        switch (msg.what){
            case 1:
                Toast.makeText(activity,"主线程接收: "+msg.obj,Toast.LENGTH_LONG).show();
                break;
        }
    }
}
@Override
protected void onDestroy() {
    super.onDestroy();
    if (runnable!=null){
        runnable=null;
    }
    if (myHandler!=null){
        myHandler.removeCallbacksAndMessages(null);
        myHandler=null;
    }
}
}
复制代码

SingleManager:

  public class SingleManager {
  private static SingleManager instance = null;

  private  Context context = null;

  private SingleManager(Context context){
      this.context = context;
  }
  public static SingleManager getInstance(Context context){
      if (instance==null){
          // FIXME: 2021/10/30 使用context.getApplicationContext()和app生命周期一样长的进行避免内存泄露
          instance = new SingleManager(context.getApplicationContext());
      }
      return instance;
  }

  public void get(){
      LogUtils.e(" logcat context:  "+ context );
  }
  }
复制代码

经过代码优化,通过Memory Profiler和 MAT工具分析结果,MemoryMonitorActivity不存在内存泄露了。

作为Android开发者,我们需要必备这个基本技能,需要熟悉Memory Profiler和 MAT内存工具的使用,开发过程中会经常使用到的。

おすすめ

転載: juejin.im/post/7084594001516445710