[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
相关文章
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
764 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
1211 6
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
375 0
|
存储 Android开发 数据安全/隐私保护
Thanox安卓系统增加工具下载,管理、阻止、限制后台每个APP运行情况
Thanox是一款Android系统管理工具,专注于权限、后台启动及运行管理。支持应用冻结、系统优化、UI自定义和模块管理,基于Xposed框架开发,安全可靠且开源免费,兼容Android 6.0及以上版本。
1874 4
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
804 11
移动端数据抓取:Android App的TLS流量解密方案
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
628 1
|
Arthas 监控 Java
Arthas mc(Memory Compiler/内存编译器 )
Arthas mc(Memory Compiler/内存编译器 )
826 6
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
781 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
872 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
Java Android开发 开发工具
Android Studio调试功能使用总结【转】
这段时间一直在使用Intellij IDEA, 今天把调试区工具的使用方法记录于此。   先编译好要调试的程序。 1.设置断点 选定要设置断点的代码行,在行号的区域后面单击鼠标左键即可。   2.开启调试会话 点击红色箭头指向的小虫子,开始进入调试。
1154 0

热门文章

最新文章