[Android Memory] App调试内存泄露之Context篇(下)

简介:

转载地址:http://www.cnblogs.com/qianxudetianxia/p/3655475.html

5. AsyncTask对象

    我N年前去盛大面过一次试,当时面试官极力推荐我使用AsyncTask等系统自带类去做事情,当然无可厚非。

    但是AsyncTask确实需要额外注意一下。它的泄露原理和前面Handler,Thread泄露的原理差不多,它的生命周期和Activity不一定一致。

    解决方案是:在activity退出的时候,终止AsyncTask中的后台任务。

    但是,问题是如何终止?

    AsyncTask提供了对应的API:public final boolean cancel (boolean mayInterruptIfRunning)。

    它的说明有这么一句话:

// Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason.
// If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

  cancel是不一定成功的,如果正在运行,它可能会中断后台任务。怎么感觉这话说的这么不靠谱呢?

    是的,就是不靠谱。

    那么,怎么才能靠谱点呢?我们看看官方的示例:

复制代码
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             // 注意下面这行,如果检测到cancel,则及时退出
             if (isCancelled()) break;
         }
         return totalSize;
     }
 
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
 
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }
复制代码

官方的例子是很好的,在后台循环中时刻监听cancel状态,防止没有及时退出。

      为了提醒大家,google特意在AsyncTask的说明中撂下了一大段英文:

// AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as Executor, ThreadPoolExecutor and FutureTask.

可怜我神州大陆幅员辽阔,地大物博,什么都不缺,就是缺对英语阅读的敏感。

    AsyncTask适用于短耗时操作,最多几秒钟。如果你想长时间耗时操作,请使用其他java.util.concurrent包下的API,比如Executor, ThreadPoolExecutor 和 FutureTask.

    学好英语,避免踩坑!

 

6. BroadcastReceiver对象

    ... has leaked IntentReceiver ... Are you missing a call to unregisterReceiver()?

    这个直接说了,种种原因没有调用到unregister()方法。

    解决方法很简单,就是确保调用到unregister()方法

    顺带说一下,我在工作中碰到一种相反的情况,receiver对象没有registerReceiver()成功(没有调用到),于是unregister的时候提示出错:

// java.lang.IllegalArgumentException: Receiver not registered ...

  有两种解决方案:

    方案一:在registerReceiver()后设置一个FLAG,根据FLAG判断是否unregister()。网上搜到的文章几乎都这么写,我以前碰到这种bug,也是一直都这么解。但是不可否认,这种代码看上去确实有点丑陋。

    方案二:我后来无意中听到某大牛提醒,在Android源码中看到一种更通用的写法:

复制代码
// just sample, 可以写入工具类
// 第一眼我看到这段代码,靠,太粗暴了,但是回头一想,要的就是这么简单粗暴,不要把一些简单的东西搞的那么复杂。
private void unregisterReceiverSafe(BroadcastReceiver receiver) {
    try {
        getContext().unregisterReceiver(receiver);
    } catch (IllegalArgumentException e) {
        // ignore
    }
}
复制代码

7. TimerTask对象

    TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。

复制代码
private void startTimer(){ 
    if (mTimer == null) { 
        mTimer = new Timer(); 
    } 
 
    if (mTimerTask == null) { 
        mTimerTask = new TimerTask() { 
            @Override 
            public void run() { 
                // todo
            } 
        }; 
    } 
 
    if(mTimer != null && mTimerTask != null ) 
        mTimer.schedule(mTimerTask, 1000, 1000); 
 
} 
复制代码

泄露的点是,忘记cancel掉Timer和TimerTask实例。cancel的时机同cursor篇说的,在合适的时候cancel。

 

复制代码
private void cancelTimer(){ 
        if (mTimer != null) { 
            mTimer.cancel(); 
            mTimer = null; 
        } 
        if (mTimerTask != null) { 
            mTimerTask.cancel(); 
            mTimerTask = null; 
        }
    }
复制代码

 

8. Observer对象。

    Observer对象的泄露,也是一种常见、易发现、易解决的泄露类型。

    先看一段正常的代码:

复制代码
// 其实也非常简单,只不过ContentObserver是系统的例子,有必要单独拿出来提示一下大家,不可掉以轻心
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        // todo
    }
};
 
@Override
public void onStart() {
    super.onStart();
 
    // register the observer
    getContentResolver().registerContentObserver(Settings.Global.getUriFor(
            xxx), false, mSettingsObserver);
}
 
@Override
public void onStop() {
    super.onStop();
 
    // unregister it when stoping
    getContentResolver().unregisterContentObserver(mSettingsObserver);
 
}
复制代码

看完示例,我们来看看病例:

复制代码
private final class SettingsObserver implements Observer {
    public void update(Observable o, Object arg) {
        // todo ...
    }  
}
 
 mContentQueryMap = new ContentQueryMap(mCursor, Settings.System.XXX, true, null);
 mContentQueryMap.addObserver(new SettingsObserver());
复制代码

    靠,谁这么偷懒,把SettingObserver搞个匿名对象传进去,这可如何是好?

    所以,有些懒是不能偷的,有些语法糖是不能吃的。

    解决方案就是, 在不需要或退出的时候delete这个Observer。

 

复制代码
private Observer mSettingsObserver;
@Override
public void onResume() {
    super.onResume();
    if (mSettingsObserver == null) {
        mSettingsObserver = new SettingsObserver();
    }  
    mContentQueryMap.addObserver(mSettingsObserver);
}
 
@Override
public void onStop() {
    super.onStop();
    if (mSettingsObserver != null) {
        mContentQueryMap.deleteObserver(mSettingsObserver);
    }  
    mContentQueryMap.close();
}
复制代码

 

注意一点,不同的注册方法,不同的反注册方法。

复制代码
// 只是参考,不必死板
/*
addCallback             <==>     removeCallback
registerReceiver        <==>     unregisterReceiver
addObserver             <==>     deleteObserver
registerContentObserver <==>     unregisterContentObserver
... ...
*/
复制代码

9. Dialog对象

    android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@438afa60 is not valid; is your activity running?

    一般发生于Handler的MESSAGE在排队,Activity已退出,然后Handler才开始处理Dialog相关事情。

    关键点就是,怎么判断Activity是退出了,有人说,在onDestroy中设置一个FLAG。我很遗憾的告诉你,这个错误很有可能还会出来。

    解决方案是:使用isFinishing()判断Activity是否退出。

 

复制代码
Handler handler = new Handler() {
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case MESSAGE_1:
            // isFinishing == true, 则不处理,尽快结束
            if (!isFinishing()) {
                // 不退出
                // removeDialog()
                // showDialog()
            }  
            break;
        default:
            break;
        }  
        super.handleMessage(msg);
    }  
};
复制代码

 

  早完早释放!

 

10. 其它对象

    以Listener对象为主,"把自己搭进去了,切记一定要及时把自己放出来"。

 

11. 小结

     结合本文Context篇和前面Cursor篇,我们枚举了大量的泄露实例,大部分根本原因都是相似的。

     通过分析这些例子后,我们应该能理解APP层90%的内存泄露情况了。

     至于怎么发现和定位内存泄露,这是另外一个有意思的话题,现在只能说,有方法有工具。

 

 

 

分类:  Android Memory
本文转自demoblog博客园博客,原文链接http://www.cnblogs.com/0616--ataozhijia/p/3755907.html如需转载请自行联系原作者

demoblog
相关文章
|
28天前
|
Shell Linux 开发工具
"开发者的救星:揭秘如何用adb神器征服Android设备,开启高效调试之旅!"
【8月更文挑战第20天】Android Debug Bridge (adb) 是 Android 开发者必备工具,用于实现计算机与 Android 设备间通讯,执行调试及命令操作。adb 提供了丰富的命令行接口,覆盖从基础设备管理到复杂系统操作的需求。本文详细介绍 adb 的安装配置流程,并列举实用命令示例,包括设备连接管理、应用安装调试、文件系统访问等基础功能,以及端口转发、日志查看等高级技巧。此外,还提供了常见问题的故障排除指南,帮助开发者快速解决问题。掌握 adb 将极大提升 Android 开发效率,助力项目顺利推进。
46 0
|
5天前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
20 4
Android性能测试——发现和定位内存泄露和卡顿
|
7天前
|
存储 网络协议 大数据
一文读懂RDMA: Remote Direct Memory Access(远程直接内存访问)
该文档详细介绍了RDMA(远程直接内存访问)技术的基本原理、主要特点及其编程接口。RDMA通过硬件直接在应用程序间搬移数据,绕过操作系统协议栈,显著提升网络通信效率,尤其适用于高性能计算和大数据处理等场景。文档还提供了RDMA编程接口的概述及示例代码,帮助开发者更好地理解和应用这一技术。
|
20天前
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
39 0
安卓系统调试与优化:(一)bootchart 的配置和使用
|
23天前
|
域名解析 网络协议 网络安全
【App Service】遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的调试小工具
【App Service】遇见本地访问Azure App Service应用慢或者是调用第三方接口慢的调试小工具
|
3天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
13 0
|
1月前
|
设计模式 uml
在电脑主机(MainFrame)中只需要按下主机的开机按钮(on()),即可调用其它硬件设备和软件的启动方法,如内存(Memory)的自检(check())、CPU的运行(run())、硬盘(Hard
该博客文章通过一个电脑主机启动的示例代码,展示了外观模式(Facade Pattern)的设计模式,其中主机(MainFrame)类通过调用内部硬件组件(如内存、CPU、硬盘)和操作系统的启动方法来实现开机流程,同时讨论了外观模式的优缺点。
|
1月前
|
编解码 Android开发 UED
【性能狂飙!】揭秘Android应用极速变身秘籍:内存瘦身+用户体验升级,打造丝滑流畅新境界!
【8月更文挑战第12天】构建高效Android应用需全方位优化,尤其重视内存管理和用户体验。通过弱引用降低内存占用,懒加载资源减少启动负担。运用Kotlin协程确保UI流畅不阻塞,响应式设计适配多屏需求。这些策略共同提升了应用性能与用户满意度。
44 1
|
1月前
|
存储 缓存 监控
托管内存(Managed Memory)
托管内存(Managed Memory)