安卓性能优化(最全)——内存优化


其实现在的手机越做越好了,手机的内存也是越来越大,6G已经是标配了,8G ,12G也是很常见了。但是,作为一款优秀的安卓开发,内存优化的问题是永恒的话题。
下面就来看一下,主要从哪些方面来优化内存:

一、防止内存泄露

1、单例模式导致的内存泄露

单例模式在安卓开发中还是会经常用到的,但是如果使用不当,还是会造成内存泄漏。因为是单例,所以使得该对象的生命周期跟整个app一样长。但是若有一个对象已经没用了用处,但是单例还持有该对象的引用。那么就会造成内存泄漏。

public class AppSettings {
    private static AppSettings sInstance;
    private Context context;

    private AppSettings(Context context){
        this.context = context;
    }

    public static AppSettings getInstance(Context context){
        if(sInstance == null){
            sInstance = new AppSettings(context);
        }
        return sInstance;
    }
}

如上代码。如果调用getInstance 方法传入的Context 是activity 或者是 Service ,就会造成内存泄漏。
可以修改如下:

	private AppSettings(Context context){
        this.context = context.getApplicationContext();
    }

ApplicationContext 的生命周期跟整个程序生命周期一样,就不会造成内存泄漏。

2、静态变量导致的内存泄漏

public class TestScrollActivity extends AppCompatActivity {
    private static Info info;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_scroll);
        
        if(info == null){
            info = new Info(this);
        }
    }
}

class Info{
    Activity activity;
    public Info(Activity activity){
        this.activity = activity;
    }
}

如上代码。静态变量的生命周期必然是比activity 长,所以也会造成无法释放activity。

3、非静态内部类导致内存泄漏

在Java中非静态内部类或者匿名内部类都是默认持有外部类的引用的。比如handle。

public class TestScrollActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_scroll);

    }
}

如上代码。mHandler会一直持有activity 的引用,导致activity无法被释放。
解决方法,使用静态的内部类。

4、未取消的注册或者回调造成内存泄漏

比如广播的注册,如果不取消注册则会造成内存泄漏。

5、Timer 和 和 TimerTask 导致内存泄露

Timer 和 TimerTask 在 Android 中通常会被用来做一些计时或循环任务,比如实现无限轮播的
ViewPager :

public class MainActivity extends AppCompatActivity {
    private ViewPager mViewPager;
    private PagerAdapter mAdapter;
    private Timer mTimer;
    private TimerTask mTimerTask;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        mTimer.schedule(mTimerTask, 3000, 3000);
    }
    private void init() {
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
        mAdapter = new ViewPagerAdapter();
        mViewPager.setAdapter(mAdapter);
        mTimer = new Timer();
        mTimerTask = new TimerTask() {
            @Override
            public void run() {
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        loopViewpager();
                    }
                });
            }
        };
    }
    private void loopViewpager() {
        if (mAdapter.getCount() > 0) {
            int curPos = mViewPager.getCurrentItem();
            curPos = (++curPos) % mAdapter.getCount();
            mViewPager.setCurrentItem(curPos);
        }
    }
    private void stopLoopViewPager() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer.purge();
            mTimer = null;
        }
        if (mTimerTask != null) {
            mTimerTask.cancel();
            mTimerTask = null;
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopLoopViewPager();
    }
}

当我们 Activity 销毁的时,有可能 Timer 还在继续等待执行 TimerTask ,它持有 Activity 的引用不
能被回收,因此当我们 Activity 销毁的时候要立即 cancel 掉 Timer 和 TimerTask ,以避免发生内存
泄漏。

6、集合中的对象未清理造成内存泄露

这个比较好理解,如果一个对象放入到 ArrayList 、 HashMap 等集合中,这个集合就会持有该对象
的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而
此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那
些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合 remove ,或
者 clear 集合,以避免内存泄漏。

7、资源未关闭或释放导致内存泄露

在使用 IO 、 File 流或者 Sqlite 、 Cursor 等资源时要及时关闭。这些资源在进行读写操作时通常都
使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。
因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

8、属性动画造成内存泄露

动画同样是一个耗时任务,比如在 Activity 中启动了属性动画( ObjectAnimator ),但是在销毁
的时候,没有调用 cancle 方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,
动画引用所在的控件,所在的控件引用 Activity ,这就造成 Activity 无法正常释放。因此同样要
在 Activity 销毁的时候 cancel 掉属性动画,避免发生内存泄漏。

	@Override 
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }

9、WebView 造成内存泄露

关于 WebView 的内存泄露,因为 WebView 在加载网页后会长期占用内存而不能被释放,因此我
们在 Activity 销毁后要调用它的 destory() 方法来销毁它以释放内存。
另外在查阅 WebView 内存泄露相关资料时看到这种情况:
Webview 下面的 Callback 持有 Activity 引用,造成 Webview 内存无法释放,即使是调用了
Webview.destory() 等方法都无法解决问题(Android5.1 之后)。
最终的解决方案是:在销毁 WebView 之前需要先将 WebView 从 父容器中移除,然后在销毁 WebView 。
详细分析过程请参考这篇文章:WebView 内存泄漏解决方法

	@Override
    protected void onDestroy() {
        super.onDestroy();
// 先从父控件中移除 WebView
        mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }

总结
内存泄露在 Android 内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄露我们
不一定就能注意到,所有在编码的过程要养成良好的习惯。总结下来只要做到以下这几点就能避
免大多数情况的内存泄漏:
构造单例的时候尽量别用 Activity 的引用;
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务、属性动画在 Activity 销毁时记得 cancel ;
文件流、 Cursor 等资源及时关闭;
Activity 销毁时 WebView 的移除和销毁。

二、资源的复用

1、bitmap的复用

比如一个图片,点击之后要更换,那么此时最好就用bitmap的复用。避免每次新建bitmap ,以此减少内存的开销。
当然使用Glide框架来加载图片也是可以的,Glide默认是实现了bitmap的复用。

2、数据过长时,不要使用NestedScrollView嵌套RecyclerView

NestedScrollView嵌套RecyclerView时,默认是一次性加载recyclerview的所有项。所以也不存在ViewHolder的复用了。如果数据量过多,就会造成OOM。
解决办法:给RecyclerView添加 HeadView 以及 FootView。
RecyclerView添加 HeadView 以及 FootView

三、减少内存开销

1、使用轻量的数据结构

如使用ArrayMap/SparseArray替代HashMap,HashMap更耗内存,因为它需要额外的实例对象来记录Mapping操作,SparseArray更加高效,因为它避免了Key Value的自动装箱,和装箱后的解箱操作。

2、尽量不使用枚举类型

可以用静态常量或者注解@IntDef替代

3、Bitmap优化

a.尺寸压缩:通过InSampleSize设置合适的缩放
b.颜色质量:设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
c.inBitmap:使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在Android 4.4之前只能重用相同大小的Bitmap的内存,而Android 4.4及以后版本则只要后来的Bitmap比之前的小即可。使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存供其使用,而使用了inBitmap参数后,多个Bitmap可以复用一块内存,这样可以提高性能。

4、StringBuilder替代String

在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

5、避免在类似onDraw这样的方法中创建对象

因为它会迅速占用大量内存,引起频繁的GC甚至内存抖动

6、可以使用样式文件来代替图片

比如一些按钮的边框之类的,那么尽量用xml文件来代替图片。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值