美文网首页
android 内存泄漏(多篇文章摘录)

android 内存泄漏(多篇文章摘录)

作者: 肉团先生 | 来源:发表于2016-02-20 15:23 被阅读760次

    什么叫内存泄漏呢?

    gc没有办法回收activity的内存

    那什么情况下会GC呢?

    • 屏幕旋转的时候
    • 内存不够用的时候。

    什么情况会发生内存泄漏呢?

    静态资源问题

    静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存.

    例子:

    public class MyCustomResource {
        //静态变量drawable
        private static Drawable drawable;
        private View view;
    
        public MyCustomResource(Context context) {
            Resources resources = context.getResources();
            drawable = resources.getDrawable(R.drawable.ic_launcher);
            view = new View(context);
            view.setBackgroundDrawable(drawable);
        }
    }
    //查看setBackgroundDrawable的源代码:
    public  void  setBackgroundDrawable(Drawable background) { 
    ..........
    /**此处的this就是当前View对象,而View对象又是有Context对象获得 因此,变量background持有View对象的引用,View持有Context的引用, 所有background间接持有Context对象的引用了*/
    background.setCallback(this);
    .......
    }
    

    分析:
    此处的background.setCallback(this);this是当前的view的对象。由于background是一个静态变量,会一直持有View对象的引用,而然View对象又是由Context对象创建出来的,因此background会间接持有Context的对象的引用
    所以:
    该Context对应的Activity退出finish掉的时候其实该Activity是不能完全释放内存的
    值得注意的是:
    代码是由于静态资源drawable持有View对象的引用导致内存泄漏隐患的,并不是由于context.getResource导致内存泄漏,因此如果你想通过context.getApplicaitonContext来获取getResource是解决不了内存泄漏的.
    另外提一点:(使用getApplicaitonContext的错误)
    android.app.Application cannot be cast to android.app.Activity

    因此:在android 3.0 中:
    修改了setBackgroundDrawable内部方法中的 background.setCallback(this);方法。里面的实现使用了弱引用来持有View对象的引用,从而避免了内存泄漏隐患

    总结:以后代码中避免使用静态资源,或者使用弱引用来解决相应的问题也是可以的。

    单例模式导致内存泄漏(单例中的类是静态的,引用context将导致以上的问题)

    public class CustomManager {
        private static CustomManager sInstance;
        public static CustomManager getInstance(Context context) {
            if (sInstance == null) {
                sInstance = new CustomManager(context);//使用context的引用。
            }
            return sInstance;
        }
    
        private Context mContext;
        private CustomManager(Context context) {
            mContext = context;
        }
    }
    

    单例模式使用的是静态类的方式,让该对象在整个应用的内存中保持一份该对象,从而减少对多次创建对象带来的资源浪费
    同样的问题:
    在创建该单例的时候使用了生命周期端的Context对象的引用,如果你是在Application中创建以上单例的话是木有任何问题的。因为Application的Context生命周期是整个应用,和单例的生命周期一样,因此不会导致内存泄漏。但是,如果你是在Activity中创建以上单例的话,将一样导致跟上一个一样的内存问题。
    所以讲以上的代码改成:

    if (sInstance == null) { 
    sInstance = new CustomManager(context.getApplicationContext()); }//注意这里的不同。
    return sInstance;
    

    以上全摘自:Android Context 是什么?

    通过上面大家可能对Context比较模糊。请阅读:Android Context 是什么?
    记录几点:

    • getApplication和getApplicationContext返回同一个Application对象,只是里面方法调用的成员返回不同。
    • 所有Context都是在应用的主线程ActivityThread中创建的
    • 尽量少用Context对象去获取静态变量,静态方法,以及单例对象。以免导致内存泄漏
    • 在创建与UI相关的地方,比如创建一个Dialog,或者在代码中创建一个TextView,都用Activity的Context去创建。然而在引用静态资源,创建静态方法,单例模式等情况下,使用生命周期更长的Application的Context才不会导致内存泄漏

    内部类Handler类引起内存泄漏

    在Activity中定义了一个内部Handler类:

    public class MainActivity extends Activity {
        private  Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //TODO handle message...
            }
        };
        @TargetApi(11)
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.sendMessageDelayed(Message.obtain(), 60000);//延迟一分钟执行
            //just finish this activity
            finish();//关闭activity
        }
    }
    

    以上代码将会导致内存泄漏的问题(原因):

    • 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象

    Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity(从以上原因可知道)。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

    修改:将Handler改为静态类,并使用WeakReference来保持外部的activity对象。

    private Handler mHandler = new MyHandler(this);
    private static class MyHandler extends Handler{
        private final WeakReference<Activity> mActivity;
        public MyHandler(Activity activity) {
            mActivity = new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            System.out.println(msg);
            if(mActivity.get() == null) {
                return;
            }
        }
    }
    

    结论:当你在Activity中使用内部类的时候,需要时刻考虑您是否可以控制该内部类的生命周期,如果不可以,则最好定义为静态内部类

    以上摘自:内部Handler类引起内存泄露

    另一种解决的方式:
    activtyonDestroy方法对handler的取消:

    public void onDestroy() {
        mHandler.removeMessages(MESSAGE_1);
        mHandler.removeMessages(MESSAGE_2);
        mHandler.removeMessages(MESSAGE_3);
        mHandler.removeMessages(MESSAGE_4);
        // ... ...
        mHandler.removeCallbacks(mRunnable);
        // ... ...
    }
    或者:
    public void onDestroy() {
       mHandler.removeCallbacksAndMessages(null);
    }
    

    Thread对象

    同上:Thread的生命周期不一定是和Activity生命周期一致。
    同上的解决方式为:

    • 改为静态内部类
    • 采用弱引用来保存Context引用

    正确的使用为:

     public class ThreadAvoidActivity extends Activity {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new MyThread(this).start();
        }
        private void dosomthing() {//dosomthing是ThreadAvoidActivity的方法
        }
        private static class MyThread extends Thread {
            WeakReference<ThreadAvoidActivity> mThreadActivityRef;//弱引用
            public MyThread(ThreadAvoidActivity activity) {
                mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
                        activity);
            }
            @Override
            public void run() {//先判断是否为null,回收掉
                super.run();
                if (mThreadActivityRef == null)
                    return;
                if (mThreadActivityRef.get() != null)
                    mThreadActivityRef.get().dosomthing();//通过弱引用得到外部的activity来调用dosomthing方法
                // dosomthing
            }
        }
    }
    

    以上切断两个对象的双向强引用链接

    1. 静态内部类:切断Activity 对于 MyThread的强引用。
    2. 弱引用: 切断MyThread对于Activity 的强引用

    AsynTask 内部类

    跟线程一样的原理。
    AsyncTask内部的实现机制是运用了ThreadPoolExcutor, 该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的。
    你可能会说:AsynTask不是有cancel的方法么?对的,是有这个方法,但还是不一定能取消。通过源码可以知道:

    public final boolean cancel(boolean mayInterruptIfRunning) {
     mCancelled.set(true); 
     return mFuture.cancel(mayInterruptIfRunning);
     }
    

    问题归因于mFuture.cancel(mayInterruptIfRunning);是否能取消正在执行的任务呢?
    通过线程执行者(九)执行者取消一个任务,文章不是重点,重点是评论,提出了是否能真正取消任务的质疑
    查阅:Android学习系列(37)--App调试内存泄露之Context篇(下),这里引用官方的言语:** cancel是不一定成功的。**

    总结:只要是使用线程正在进行执行任务,都不一定能够取消。大多数是使用:Thread.interrupt()进行中断,但不一定成功的。所以最好的使用静态内存类或者使用弱引用

    官方的例子:

    private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
         protected Long doInBackground(URL... urls) {
             int count = urls.length;
             long totalSize = 0;
             for (int i = 0; i < count; i++) {
                 totalSize += Downloader.downloadFile(urls[i]);
                 publishProgress((int) ((i / (float) count) * 100));
                 // Escape early if cancel() is called
                 // 注意下面这行,如果检测到cancel,则及时退出
                 if (isCancelled()) break;
             }
             return totalSize;
         }
     
         protected void onProgressUpdate(Integer... progress) {
             setProgressPercent(progress[0]);
         }
     
         protected void onPostExecute(Long result) {
             showDialog("Downloaded " + result + " bytes");
         }
     }
    

    在后台循环中时刻监听cancel状态,防止没有及时退出。

    所以:使用cancel并不靠谱,那么如何书写呢?跟Thread的思路是一样,差不多:

    /**
     *
     * 弱引用
     * @version 1.0.0
     * @author Abay Zhuang <br/>
     *         Create at 2014-7-17
     */
    public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
        extends AsyncTask<Params, Progress, Result> {
    protected WeakReference<WeakTarget> mTarget;
    public WeakAsyncTask(WeakTarget target) {
        mTarget = new WeakReference<WeakTarget>(target);
    }
    @Override
    protected final void onPreExecute() {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            this.onPreExecute(target);
        }
    }
    @Override
    protected final Result doInBackground(Params... params) {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            return this.doInBackground(target, params);
        } else {
            return null;
        }
    }
    @Override
    protected final void onPostExecute(Result result) {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            this.onPostExecute(target, result);
        }
    }
    protected void onPreExecute(WeakTarget target) {
        // Nodefaultaction
    }
    protected abstract Result doInBackground(WeakTarget target,
            Params... params);
    protected void onPostExecute(WeakTarget target, Result result) {
        // Nodefaultaction
    }
    }
    

    BroadcastReceiver对象

    原因:没有取消注册。
    直接:getContext().unregisterReceiver(receiver);即可.
    注册与反注册:

    addCallback             <==>     removeCallback
    registerReceiver        <==>     unregisterReceiver
    addObserver             <==>     deleteObserver
    registerContentObserver <==>     unregisterContentObserver
    

    TimerTask对象

    TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。
    要在合适的时候进行Cancel即可。

    private void cancelTimer(){ 
            if (mTimer != null) { 
                mTimer.cancel(); 
                mTimer = null; 
            } 
            if (mTimerTask != null) { 
                mTimerTask.cancel(); 
                mTimerTask = null; 
            }
        }
    

    剩下几个小点:

    • Dialog对象:使用isFinishing()判断Activity是否退出。才可以showDialog
    • Bitmap没调用recycle()
    • 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们
    • 构造Adapter时,没有使用缓存的 convertView
    • 集合容器对象没清理造成的内存泄露(尤其是static的集合,需要clear清理)
    • WebView对象没有销毁。它的destory()函数来销毁它

    总结:

    泄漏都是有生命周期和Activity不一定一致,导致无法进行回收。

    只要是使用线程正在进行执行任务,都不一定能够取消。大多数是使用:Thread.interrupt()进行中断,但不一定成功的。所以最好的使用静态内存类**或者使用弱引用

    实战检测定位内存泄露

    1. 使用MAT工具定位Android应用内存泄漏
    # 使用Android studio分析内存泄露

    资料:

    Android Context 是什么?
    # Android内存泄露之Thread
    # Android内存泄露之Handler
    Android App 内存泄露之资源
    Android内存泄露之DDMS –> Heap工具,这个系列的文章。
    Android学习系列(37)--App调试内存泄露之Context篇(下)
    Android内存泄露的整理

    相关文章

      网友评论

          本文标题:android 内存泄漏(多篇文章摘录)

          本文链接:https://www.haomeiwen.com/subject/zouvkttx.html