前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Eight Ways Your Android App Can STOP Leaking Memory

Eight Ways Your Android App Can STOP Leaking Memory

作者头像
用户9732312
发布2022-05-13 18:04:33
2100
发布2022-05-13 18:04:33
举报
文章被收录于专栏:ADAS性能优化

In part one of this blog post “Eight Ways Your Android App Can Leak Memory”, we went over eight different ways your code can cause your Android application to leak memory. Specifically, all eight leaks were leaking an Activity instance, which is particularly dangerous because activities have a very large footprint in memory. Despite all the different ways to leak an Activity that we presented, every problem really boils down to one fundamental issue: preserving a reference to the Activity after its defined lifecycle has completed. Thankfully, once a leak has been tracked down and identified, the solution is usually rather simple.

1. Static Activities

This leak

代码语言:javascript
复制
    private static MainActivity activity;

    void setStaticActivity() {
        activity = this;
    }

was constructed to reveal the consequences of holding a reference to your activity in a static class variable that will outlive any particular instance of the activity.The activity’s class object is app-global and will preserve the activity in memory for the indefinite future. There are legitimate reasons a developer may choose to do this and therefore we need to come up with a solution that does not prevent the activity from being garbage collected once it is ready to be destroyed. Android provides a special set of objects https://developer.android.com/reference/java/lang/ref/package-summary.html#classes that allow the developer to control the “strength” of a reference. The activity is being leaked because it continues to be strongly referenced even after the intention was to destroy it and release it from memory. The garbage collector cannot clean up the activity’s memory as long as this reference exists. Therefore we can solve the leak by using a WeakReference https://developer.android.com/reference/java/lang/ref/WeakReference.html. Weak references do not prevent the object’s memory from being reclaimed and therefore if only weak references to the object remain, it will become eligible for garbage collection.

代码语言:javascript
复制
    private static WeakReference<MainActivity> activityReference;

    void setStaticActivity() {
        activityReference = new WeakReference<MainActivity>(this);
    }

2. Static Views

Statically maintaining a reference to a View

代码语言:javascript
复制
    private static View view;

    void setStaticView() {
        view = findViewById(R.id.sv_button);
    }

from an Activity is just as problematic as a direct reference to the Activity itself because Views contain a reference to the Activity they’re housed in. Therefore a WeakReference would be just as effective in solving this leak. However, we can also manually clear the reference when it is clear that the Activity object is at the end of its lifecycle and would appreciate a meeting with the garbage collector. To do this, we simply override the Activity’s onDestroy() method, which is guaranteed to be called at end-of-life, and set the reference to null.

代码语言:javascript
复制
    private static View view;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (view != null) {
            unsetStaticView();
        }
    }

    void unsetStaticView() {
        view = null;
    }

3. Inner Classes

The leak

代码语言:javascript
复制
    private static Object inner;

    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }

we created is very similar to the two above. Developers are often cautioned to avoid non-static nested classes, known as inner classes https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html, because they hold an implied reference to the outer class and therefore it is very easy to inadvertently leak your Activity. However there are benefits to using an inner class such as being able to access even the private members of the outer class and as long as we are aware of the lifetime of our reference, we can prevent a leak. Once again, we naively created a static reference, this time to an instance of our inner class. To solve the leak, we can just as easily avoid declaring the reference as static and continue with business as usual.

代码语言:javascript
复制
    private Object inner;

    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }

4-7. Anonymous Classes

So far the root cause of every memory leak we’ve seen is an app-global static reference that either directly or indirectly through a chain of other references holds onto the Activity object and prevents it from being garbage collected. The leaks we created using AsyncTask:

代码语言:javascript
复制
    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                while(true);
            }
        }.execute();
    }

Handler:

代码语言:javascript
复制
    void createHandler() {
        new Handler() {
            @Override public void handleMessage(Message message) {
                super.handleMessage(message);
            }
        }.postDelayed(new Runnable() {
            @Override public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }

Thread:

代码语言:javascript
复制
    void spawnThread() {
        new Thread() {
            @Override public void run() {
                while(true);
            }
        }.start();
    }

and TimerTask:

代码语言:javascript
复制
    void scheduleTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }

are all caused by declaring an anonymous class https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html. An anonymous class is actually just a specialized inner class whose main benefit is allowing code to be written concisely. Whenever a particular task only requires a one-time subclassing of a particular class, Java provides syntax sugar that lets the subclass be declared in-place as an expression with minimal syntax. This is great for writing clean code but can lead to a new, but closely related set of memory leaks. As we saw above for inner classes, their instances are completely harmless as long as you don’t create a reference to them that outlives the lifecycle of the Activity they’re declared in. However, these particular anonymous classes are all being used to spawn background threads for the application. These java threads are app-global and maintain a reference to the object that created them, the anonymous class instance, which in turn holds a reference to the outer class because it’s a non-static inner class. The threads may run in perpetuity and therefore persist a memory chain to the Activity that keeps the garbage collector from doing its job even after the Activity’s lifecycle is complete. This time we cannot just avoid declaring the reference as static because the thread is global to the state of the app. Instead for the sake of avoiding an Activity leak, we must abandon the conciseness of an anonymous class and declare each of our subclasses as static nested classes. Once a nested class is static, it no longer maintains a reference to the outer class instances and breaks our reference chain. There is nothing that inherently differentiates these particular classes and we can apply the same technique to AsyncTask:

代码语言:javascript
复制
    private static class NimbleTask extends AsyncTask<Void, Void, Void> {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }

    void startAsyncTask() {
        new NimbleTask().execute();
    }

Handler:

代码语言:javascript
复制
    private static class NimbleHandler extends Handler {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }

    private static class NimbleRunnable implements Runnable {
        @Override public void run() {
            while(true);
        }
    }

    void createHandler() {
        new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1);
    }

and TimerTask:

代码语言:javascript
复制
    private static class NimbleTimerTask extends TimerTask {
        @Override public void run() {
            while(true);
        }
    }

    void scheduleTimer() {
        new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
    }

However if you insist on using an anonymous class, you always have the option of terminating the java thread that’s keeping the object alive longer than the Activity. The following is just one of many ways to accomplish this for an anonymously declared Thread. Since we want the Thread to end along with the Activity, all we have to do is design the run loop to rely on the Thread’s interrupted flag and then set the flag in the Activity’s onDestroy() method.

代码语言:javascript
复制
    private Thread thread;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (thread != null) {
            thread.interrupt();
        }
    }

    void spawnThread() {
        thread = new Thread() {
            @Override public void run() {
                while (!isInterrupted()) {
                }
            }
        }
        thread.start();
    }

8. Sensor Manager

This example

代码语言:javascript
复制
    void registerListener() {
        SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
    }

is just one of many ways that utilizing an Android system service can leak your Activity. In order to facilitate communication between the system service and the Activity, we register the Activity as a listener, and therefore create a reference chain between the service’s sensor manager event queue and our Activity. As long as our Activity remains registered with the sensor manager, the reference will not be released and the leak will persist. Once the activity is at the end of its lifetime, there really isn’t any reason it should continue to listen to events from any sensor. To solve the leak, all we need to do is unregister the listener at the Activity’s end-of-life.

代码语言:javascript
复制
    private SensorManager sensorManager;
    private Sensor sensor;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (sensor != null) {
            unregisterListener();
        }
    }

    void unregisterListener() {
        sensorManager.unregisterListener(this, sensor);
    }

Activity leaks are all rooted in the particular cases we’ve seen here and in those that are very similar to them. Now that we’ve suggested how to address these specific issues you can apply the same techniques to any future leaks that might spring up. Memory leaks are really easy to squash once they’ve been identified so as long as you’re checking for them often you can catch them early on and create the best possible experience for your users.

You can find the full source code for this example here and the very same application uploaded to NimbleDroid here.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android性能优化 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Static Activities
  • 2. Static Views
  • 3. Inner Classes
  • 4-7. Anonymous Classes
  • 8. Sensor Manager
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档