Androidの一般的な面接の質問 - メモリリークの原因と解決策

序文

少し「あなたはAndroidのAndroidのメモリリークとあなたのためのメモリ・オーバーフローの理由を理解しています、簡単にしてください」、そしてほとんどの人が上の理由や例と解決策を与えることができますが、実際のプロジェクト:最も頻繁に尋ねたインタビューすることですこれらのメモリリークは、共通の言葉遣いやソリューションです整理するために、今日、注意を払うか、メモリリークが発生することはありません。

理由

多くの人々のメモリー・リークの原理は理解しますが、再び私のために、メモリリークを防ぐために、すべての人の意識を強化するためです。それはメモリリークの原則になるとGCでのJavaについて話しています。彼はそんなに人気があるJavaオブジェクト指向プログラミングした理由はただの方法ではありません、もう一つの重要な理由があり、それはプログラマがメモリを解放することなく作業を助けることができるためであるが、Javaは、我々はその知性を考えていなかった、メモリはそれをスクラブ私たちは、固定された判定ロジックに依存しなければなりませんでした。

JavaはGCに分けることができます

参照カウントアルゴリズム

その場所を参照し、カウンタ値が1だけインクリメントされるたびに、オブジェクトのカウンタへの参照を追加しない、失敗に言及する場合、カウンタ値を1だけ減少され、被験者はいつでも0のカウンタ値が使用されなくなりました、それは、オブジェクトを再利用することができます。この原則を理解することは簡単で効率的ですが、致命的な欠陥は、互いに循環参照のオブジェクト間で解決できない問題があるです。図に示すように。

WEBP

到達可能性解析アルゴリズム

参照カウントアルゴリズム、到達可能性解析アルゴリズムのための致命的な問題は、簡単にこの問題を解決することができます。ノードがルートノードからトラバースされていない場合、アウトトラバースすることによりGCルートから到達可能性アルゴリズムはOBJ1、OBJ2、OBJ3、OBJ5すべてのルートノードからのものに示すように、オブジェクトに対応するノードは、回復可能な状態であることを示すことができますノードが到着します。これとは対照的にOBJ4、obj6、obj7それでもobj6、obj7循環参照、お互いをルートに到達するが、それでもリサイクル最後のJVMを属するオブジェクトをクリーンアップすることはできません。

WEBP

看了这些知识点,我们再来寻找内存泄漏的原因,Android是基于Java的一门语言,其垃圾回收机制也是基于Jvm建立的,所以说Android的GC也是通过可达性分析算法来判定的。但是如果一个存活时间长的对象持有另一个存活时间短的对象就会导致存活时间短的对象在GC时被认定可达而不能被及时回收也就是我们常说的内存泄漏。Android对每个App内存的使用有着严格的限制,大量的内存泄漏就可能导致OOM,也就是在new对象请求空间时,堆中没有剩余的内存分配所导致的。

既然知道了原理那么平时什么会出现这种问题和怎么合理的解决这种问题呢。下面来按实例说话。

WEBP

内存泄漏的例子

Handler

说到Handler这个东西,大家平时肯定没少用这玩意,但是要是用的不好就非常容易出现问题。举个例子

public Handler handler = new Handler(){    @Override
    public void handleMessage(Message msg) {      super.handleMessage(msg);
      toast("handlerLeakcanary");
    }
  };private void handlerLeakcanary(){
    Message message = new Message();
    handler.sendMessageDelayed(message,TIME);
  }

老实说写过代码的人肯定很多。其中不乏了解内存泄漏原理的人。但是平时需要多的时候一不小心就可能写下这气人的代码。

WEBP

了解Handler机制的人都明白,但message被Handler send出去的时候,会被加入的MessageQueue中,Looper会不停的从MessageQueue中取出Message并分发执行。但是如果Activity 销毁了,Handler发送的message没有执行完毕。那么Handler就不会被回收,但是由于非静态内部类默认持有外部类的引用。Handler可达,并持有Activity实例那么自然jvm就会错误的认为Activity可达不就行GC。这时我们的Activity就泄漏,Activity作为App的一个活动页面其所占有的内存是不容小视的。那么怎么才能合理的解决这个问题呢

1、使用弱引用

Java里面的引用分为四种类型强引用、软引用、弱引用、虚引用。如果有不明白的可以先去了解一下4种引用的区别

 public static class MyHandler extends Handler{
    WeakReference<ResolveLeakcanaryActivity> reference;    public MyHandler(WeakReference<ResolveLeakcanaryActivity> activity){
      reference = activity;
    }    @Override
    public void handleMessage(Message msg) {      super.handleMessage(msg);      if (reference.get()!=null){
        reference.get().toast("handleMessage");
      }
    }
  }

引用了弱引用就不会打扰到Activity的正常回收。但是在使用之前一定要记得判断弱引用中包含对象是否为空,如果为空则表明表明Activity被回收不再继续防止空指针异常

2、使用Handler.removeMessages();
知道原因就很好解决问题,Handler所导致的Activity内存泄漏正是因为Handler发送的Message任务没有完成,所以在onDestory中可以将handler中的message都移除掉,没有延时任务要处理,activity的生命周期就不会被延长,则可以正常销毁。

单例所导致的内存泄漏

在Android中单例模式中经常会需要Context对象进行初始化,如下简单的一段单例代码示例

public class MyHelper {  private static MyHelper myHelper;  private Context context;  private MyHelper(Context context){    this.context = context;
  }  public static synchronized MyHelper getInstance(Context context){    if (myHelper == null){
      myHelper = new MyHelper(context);
    }    return myHelper;
  }  public void doSomeThing(){

  }

}

这样的写法看起来好像没啥问题,但是一旦如下调用就会产生内存溢出

  public void singleInstanceLeakcanary(){
    MyHelper.getInstance(this).doSomeThing();
  }

首先单例中有一个static实例,实例持有Activity,但是static变量的生命周期是整个应用的生命周期,肯定是会比单个Activity的生命周期长的,所以,当Activity finish时,activity实例被static变量持有不能释放内存,导致内存泄漏。
解决办法:
1.使用getApplicationContext()

  private void singleInstanceResolve() {
    MyHelper.getInstance(getApplicationContext()).doSomeThing();
  }

2.改写单例写法,在Application里面进行初始化。

匿名内部类导致的异常

 /**
   * 匿名内部类泄漏包括Handler、Runnable、TimerTask、AsyncTask等
   */
  public void anonymousClassInstanceLeakcanary(){    new Thread(new Runnable() {      @Override
      public void run() {        try {
          Thread.sleep(TIME);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();
  }

这个和Handler内部类导致的异常原理一样就不多说了。改为静态内部类+弱引用方式调用就行了。

静态变量引用内部类

  private static Object inner;  public void innearClassLeakcanary(){    class InnearClass{

    }
    inner = new InnearClass();
  }

因为静态对象引用了方法内部类,方法内部类也是持有Activity实例的,会导致Activity泄漏
解决方法就是通过在onDestory方法中置空static变量

网络请求回调接口

    Retrofit retrofit = new Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create())
        .baseUrl("http://gank.io/api/data/")
        .build();
    Api mApi = retrofit.create(Api.class);
    Call<AndroidBean> androidBeanCall = mApi.getData(20,1);
    androidBeanCall.enqueue(new Callback<AndroidBean>() {      @Override
      public void onResponse(Call<AndroidBean> call, Response<AndroidBean> response) {
        toast("requestLeakcanary");
      }      @Override
      public void onFailure(Call<AndroidBean> call, Throwable t) {

      }
    });

这是一段很普通的请求代码,一般情况下Wifi请求很快就回调回来了,并不会导致什么问题,但是如果是在弱网情况下就会导致接口回来缓慢,这时用户很可能就会退出Activity不在等待,但是这时网络请求还未结束,回调接口为内部类依然会持有Activity的对象,这时Activity就内存泄漏的,并且如果是在Fragment中这样使用不仅会内存泄漏还可能会导致奔溃,之前在公司的时候就是写了一个Fragment,里面包含了四个网络请求,由于平时操作的时候在Wi-Fi情况下测试很难发现在这个问题,后面灰度的时候出现Crash,一查才之后当所附属的Activity已经finish了,但是网络请求未完成,首先是Fragment内存泄漏,然后调用getResource的时候返回为null导致异常。这类异常的原理和非静态内部类相同,所以可以通过static内部类+弱引用进行处理。由于本例是通过Retrofit进行,还可以在onDestory进行call.cancel进行取消任务,也可以避免内存泄漏。

RxJava异步任务

RxJava最近很火,用的人也多,经常拿来做网络请求和一些异步任务,但是由于RxJava的consumer或者是Observer是作为一个内部类来请求的时候,内存泄漏问题可能又随之而来

  @SuppressLint("CheckResult")  public void rxJavaLeakcanary(){
    AppModel.getData()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(        new Consumer<Object>() {          @Override
          public void accept(Object o) throws Exception {
            toast("rxJavaLeakcanary");
          }
        });
  }

这个代码很常见,但是consumer这个为内部类,如果异步任务没有完成Activity依然是存在泄漏的风险的。好在RxJava有取消订阅的方法可通过如下方法解决

  @Override
  protected void onDestroy() {    super.onDestroy();    if (disposable!=null && !disposable.isDisposed()){
      disposable.dispose();
    }
  }

Toast显示

看到这个可能有些人会惊讶,为啥Toast会导致内存泄漏,首先看一下

Toast.makeText(この、 "トースト"、Toast.LENGTH_SHORT)。

このコードは、あなたはそれに精通しているが、あなたがしなければ、それはメモリリークに直接つながる可能性が
、ここに行くコンテキストを渡す、とトーストは、実際のLinearLayoutトーストを持って、画面上のレイアウトを、追加、コンテキストのLinearLayoutようです初期化パラメータは、それが活動、我々はすべて知っているトーストショーを開催されていたであろう時間制限は、実際には、非同期タスクで、最終的に消えるを許可していますが、まだ活動トーストに表示された場合、破壊されているディスプレイトースト理由ライフサイクルの終わりではない、メモリリークの今回の活動を終了していません。
解決策は、直接、コード、自身のパッケージToastUtilを使用呼び出すためのApplicationContextを使用することではありません。またはgetApplicationContextで呼び出すように、トースト変数をキャンセルして表示をキャンセルすることがあります

 プライベートボイドトースト(文字列MSG){ 
    Toast.makeText(getApplicationContext()、MSG、Toast.LENGTH_SHORT).SHOW(); 
  }

概要

非常に多くの事実を感じていない読んで、原理は非常に単純なメモリリークで、彼らは本当に変更のちょうど形、新しい名前で変更します。しかし、まだエンコードで発生する可能性のあるこれらの問題に注意を払っていません。私が書くコードの原理を理解するために行った後に

おすすめ

転載: blog.51cto.com/14217562/2407919