IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 内存优化小结 -> 正文阅读

[移动开发]内存优化小结

内存泄漏(Memory Leak)

每个应用程序都需要内存来完成工作,为了确保Android 系统的每个应用都有足够的内存,Android 系统需要有效地管理内存分配。当内存不足时,Android 运行时就会触发GC,GC采用的垃圾标记算法为根搜索算法。

在这里插入图片描述
从上图可知,Obj4是可达的对象,表示它被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然相互引用,但是因为它们到GCRoots是不可达,所以它们仍旧被标记为可回收的对象。

内存泄露就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。或者是已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露

此时,如果Obj4 是一个没用的对象,但它仍与GCRoots是可达的,那么Obj4就会发生内存泄漏。

内存泄漏产生的原因,主要分为三大类:

  • 由开发人员自己编码造成的泄漏
  • 第三方框架造成的泄漏
  • 由Android 系统或者第三方ROM造成的泄漏

内存检测工具

  • Memory Profiler

内存性能分析器是 Android Profiler 中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。

Memory Profiler

  • LeakCanary

LeakCanary是Android的内存泄漏检测库,来缩小每次泄漏的原因。

LeakCanary

  • Matrix ResourceCanary

Resource Canary主要是用来检测Activit级别的内存泄漏、以及重复创建的冗余Bitmap。

ResourceCanary

内存泄漏的场景

非静态内部类的静态实例

非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接地长期维持着外部类的引用,阻止被系统回收。

    private static Object inner;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acttivity_test_resource_trace);

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createInnerClass();
            }
        });
    }
    
    private void createInnerClass(){
        inner=new Inner();
    }
    
    class Inner{}

当点击Button 时,会在createInnerClass() 方法里创建非静态内部类Inner的静态实例inner,该实例的生命周期会和应用程序一样长,并且一直持有当前Activity的引用,导致当前Activity无法被回收。

多线程相关的匿名内部类/非静态内部类

匿名内部类也会持有外部类实例的引用。多线程相关的Thread类和实现Runnable 接口的类等,它们的匿名内部类/非静态内部类如果做耗时操作就可能发生内存泄漏。

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

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               new Thread(new MyRunnable()).start();
               finish();
            }
        });
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 耗时较长任务
        }
    }

Runnable使用了匿名内部类,那么它将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。解决方法就是使用静态内部类,这样便可以避免内存泄漏。

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

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               new Thread(new MyRunnable()).start();
               finish();
            }
        });
    }
    
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 耗时较长任务
        }
    }

Handler 内存泄漏

Handler的Message被存储在MessageQueue中,有些Message并不能马上处理掉,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler是非静态的,则Handler 也会导致引用它的Activity 或者Service 不能被回收。

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

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessageDelayed(Message.obtain(),60000);
                finish();
            }
        });
    }

    Handler mHandler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

Handler 是非静态的匿名内部类的实例,它会隐性引用外部类Activity。点击Button时,Activity会结束,但是Handler 中的消息还没有被处理,因此Activity无法被回收。解决方案如下:

  MyHandler mHandler=new MyHandler();
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acttivity_test_resource_trace);

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessageDelayed(Message.obtain(),60000);
                finish();
            }
        });
    }
     // 一种解决方法:MyHandler 是静态内部类,它持有TestActivity对象使用了弱引用,这样就避免了内存泄漏。
    static class MyHandler  extends Handler{
        private WeakReference<TestActivity> mActivity;
        
        public MyHandler(TestActivity activity){
           mActivity=new  WeakReference<TestActivity>();
        }
        
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }
    
    // 另一种解决方法:在onDestroy()方法中将Callbacks 和Messages 全部清除。采用这种方法,Handle中消息可能无法全部处理完
    @Override
    public void onDestroy(){
        if(mHandler!=null){
            mHandler.removeCallbackAndMessages(null);
        }
    }

未正确使用Context

对于不是必须使用Activity的Context 的情况(Dialog 的Context 必须使用Activity的Context),可以考虑使用Application Context来替代Activity的Context,这样可以避免Activity 泄漏。

public class AppSetting{
    private Context mAppContext;
    
    private static AppSetting mAppSetting=new AppSetting();
    
    pubic static AppSetting getInstance(){
        return mAppSetting;
    }
    
    public final void setup(Context context){
        mAppContext=context;
    }
}

mAppSetting作为静态对象,其生命周期长于Activity。当进行屏幕旋转旋转时,系统会销毁当前Activity。因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity 被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄漏。解决办法就是使用Application的 Context。

public final void setup(Context context){
        mAppContext=context.getApplicationContext();
    }

静态View

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收。解决的方法就是在onDestory 方法中将静态View 置为null。

private static Button btn;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acttivity_test_resource_trace);

        btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
    
    @Override
    public void onDestroy(){
        btn=null;
    }

WebView

WebView 存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主程序进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

资源对象未关闭

资源对象比如File、Cursor等,往往都使用了缓冲,会造成内存泄漏。因此,在资源对象不使用时,一定要确保它们已经关闭并将它们的引用置为Null,通常在finally 语句中进行关闭,防止出现异常时,资源未被释放的问题。

集合中对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static 的话,那情况更加严重。

Bitmap 对象

临时创建的某个相对比较大的Bitmap对象,在经过变换得到 新的Bitmap 对象之后,应该尽快回收原始的Bitmap,这样能够更快释放原始Bitmap所占用的空间。避免静态变量持有比较大的Bitmap 对象或者其他大的数据对象,如果持有,要尽快置空该静态变量。

监听器未关闭

很多系统服务(比如TelephoneManager、SensorManager)需要register 和unregister 监听器,需要确保在合适的时候及时unregister监听器。自己手动添加的Listener,要记得在合适的时候及时移除这个Listener。

小结:内存泄漏最终导致内存溢出。

内存溢出(Out of Memory Error)

内存溢出是指当对象的内存占用已经超出分配内存的空间大小,这时未经处理的异常就会抛出。

内存溢出原因

  • 内存泄露导致

由于我们程序的失误,长期保持某些资源(如Context)的引用,垃圾回收器就无法回收它。该对象占用的内存就无法被使用,这就造成内存泄露。

  • 占用内存较多的对象

保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:29:58  更:2022-05-05 11:32:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 23:59:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码