Android优化篇之内存优化

内存优化

内存抖动

  • 在Java中每创建一个对象,就会申请一块内存,用来存储对象信息
  • 每分配一块内存,程序的可用内存就少一块
  • 当程序被占用的内存达到一个临界,垃圾回收器(GC)开始工作,释放一部分不再被使用的内存,这本身没问题
  • 但当频繁的创建对象就会造成被占用的内存不断攀升,回收后又迅速涨起来,接着又回收
  • 这种短时间内反复内存增长和回收现象就是内存抖动

可通过Android Studio的Profiler查看

引发问题

内存抖动可能导致程序卡顿甚至OOM内存溢出

卡顿

  • 内存回收在Java中采用的是GC机制,无论哪种方式实现的GC在执行时都不可避免的需要STW(Stop The World)
  • STW意味着所有的工作线程都将被暂停,虽然时间很短,但终究会存在时间成本,一两次内存回收不易被察觉,但多次内存回收集中在短时间内爆发,这就会造成较大程度的界面卡顿风险

例:当用户在界面滑动或点击某个按钮时,此时虚拟机在运行GC线程,进行内存回收,响应事件的线程就会被GC暂停,等恢复后才能响应事件,对用户的直观感受就是程序卡顿。

OOM

  • 内存抖动除了会导致卡顿外,也会造成内存溢出(OOM)
  • 若垃圾回收采用的是标记-清除算法,那么此算法会导致大量的内存碎片

当程序频繁创建与回收对象时,会导致程序中连续内存不足

例:需要创建一个占用10个格子大小内存的字节数组对象时,若可用内存中连续内存不足,就会出现OOM。虽然回收后,可用内存不止10个格子大小,但没有连续的内存。这就是内存碎片,可用的连续空间比要申请的空间小,导致这些小内存块不能被利用

Android中尽量避免在View的onDraw()中创建对象,因为onDraw()会被频繁调用

尽量避免在频繁执行或循环中创建对象

内存泄漏

程序中已动态分配的堆内存由于某种原因未被释放或无法释放,造成系统内存浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

原因

GC机制中判断一个对象是否可被回收,垃圾对象检测主要有两种算法:引用计数器法 和 可达性分析法

引用计数器法(java未使用该方法)

为每个对象设置一个引用计数器,每当有一个地方引用时,计数器+1,引用失效时,计数器-1,任何时刻计数器值为0的对象,就表示该对象不可再被使用,但当两个对象相互引用就会导致无法回收

可达性分析法(java使用该方法)

通过一系列称为"GC Roots"的对象为起始点,从这些节点向下搜索,搜索所有的引用链,当一个对象到GC Roots没有任何引用链时(即GC Roots到对象不可达),则该对象不可用

  • Java中可作为GC Roots的对象包括
  1. 方法区:类静态属性对象、常量对象
  2. 虚拟机栈(本地变量表)中的对象
  3. 本地方法栈JNI(Native方法)中的对象

示例

  1. 单例引起的内存泄漏

单例静态特性导致它的生命周期与程序生命周期一样,若有对象不再使用,却被单例持有,就会导致其无法回收,进而导致内存泄漏

kotlin 复制代码
/**
 * 若context对象传入了Activity,就会导致Activity销毁时无法被回收
 * 解决方案:传入Application上下文
 */
object CommonTool {
    lateinit var mContext: Application

    fun getContext(): Application? {
        if (::mContext.isInitialized) {
            return mContext
        }

        return null
    }
}
  1. 非静态内部类创建静态实例引起的内存泄漏

非静态内部类会默认持有外部类引用,若在外部类中去创建这个内部类对象,当频繁打开关闭Activity时,会导致重复创建对象,造成资源浪费。为避免该问题,可把该实例设为静态,解决重复创建实例问题,但会引起其他问题(静态成员变量生命周期和程序生命周期一样),静态成员变量持有Activity引用,所以Activity销毁时,无法回收

java 复制代码
/**
 * 解决方案:将Test改为静态内部类(static class Text)
 */
class XxxActivity extends AppCompatActivity {
    private static Test test = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xxx);
        if(test == null){
            test = new Test();
        }

        // ...
    }
   
    class Test {
        // ...
    }
}
  1. Handler引起的内存泄漏
java 复制代码
/**
 * 程序启动时,主线程会创建Looper对象,内部维护一个MessageQueue消息队列,按时间顺序存放Message
 * 若Handler通过内部类创建,内部类持有外部类引用(Activity),而队列中的消息target指向Handler,即消息持有Handler引用
 * 若Activity销毁时队列中的消息还未处理完,这些未处理完的消息会持有Activity引用,导致Activity无法回收
 *
 * 解决方案:Handler改为静态内部类,Activity#onDestroy()中移除队列中消息,Handler内弱引用Activity对象,让内部类不再持有外部类的引用时,程序就不允许Handler操作Activity对象
 */
class XxxActivity extends AppCompatActivity {
    XxxHandler mHandler = new XxxHandler(this);

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

         new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendMessage(Message.obtain());
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除对应的Runnable或者是Message
        // mHandler.removeCallbacks(runnable);
        // mHandler.removeMessages(what);
        mHandler.removeCallbacksAndMessages(null);
    }

    private static class XxxHandler extends Handler {
        private WeakReference<Activity> mActivity;

        public XxxHandler(Activity activity) {
            mActivity = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if (mActivity.get() == null || mActivity.isFinishing()) {
                return;
            }

            // to do something..

        }
    }
}
  1. Asynctask引起的内存泄漏

和Handler类似,Asynctask内部类持有外部类引用,改为静态内部类并在onDestroy中取消任务即可

  1. WebView引起的内存泄漏

WebView在不同的版本上和不同的机型上会存在不同的问题

  • 方式一

直接为WebView开辟一个进程,结束操作时,结束进程(System.exit(0)),进程间通讯可采用AIDL、Messager、ContentProvider、Broadcast、Intent等方式

  • 方式二

不在xml中定义,定义一个view容器(例FrameLayout(layout)),在代码中动态添加(context可使用弱引用),onDestroy()销毁时直接layout.removeAllViews(),移除WebView

  1. 资源对象未关闭引起的内存泄漏

广播、服务、数据库cursor、多媒体、文件、套接字等不使用时需关闭

  1. 三方注册资源未取消注册(例EventBus);为便于管理Activity,将其添加到Activity栈中,销毁时未从栈中移除等
相关推荐
Kapaseker29 分钟前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴32 分钟前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭11 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab12 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe17 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter