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栈中,销毁时未从栈中移除等
相关推荐
工业甲酰苯胺1 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3431 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee2 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯3 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey4 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!6 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟7 小时前
Android音频采集
android·音视频
小白也想学C8 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程8 小时前
初级数据结构——树
android·java·数据结构
闲暇部落10 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin