简述用 MAT 分析 Android 应用OOM

OOM(OutOfMemoryError) 相信是所有 Android 开发者遇到的最多的 Error,因此找出个中缘由是非常重要。

这里用到的分析工具主要是 Eclipse MAT 插件http://www.eclipse.org/mat/

Eclipse MAT(Memory Analyzer Tool)主要用于分析Java程序OOM 时 JVM 的 Heap dump文件(HPROF格式的二进制文件)。

一般的,Java 程序要生成这样的dump文件,需设置 JVM 启动参数-XX:-HeapDumpOnOutOfMemoryError(同样适用于Android应用)。

Android应用生成 dump 文件可以使用 DDMS,点击Devices工具”Dump HPROF file”,按提示框保存文件即可(如果安装了 MAT则会自动打开文件)。

具体就不再赘述…
相关阅读:

这里简单介绍一下用 MAT分析 Android 应用的内存泄露。

  1. 首先安装 MAT
    http://www.eclipse.org/mat/downloads.php下载或者用Update Sitehttp://download.eclipse.org/mat/1.4/update-site/安装
  2. 在 DDMS 中选择需要调试的设备及你的应用进程
  3. 点击“Dump HPROF file”按钮提取 dump 文件(需稍许等待)
  4. 自动用MAT 打开文件,选择“Leak Suspects Report”
  5. 睁大你的双眼揪出最有可能导致 OOM 的因素吧!

Eclipse打开后挂在Android SDK Loader 0%

如题,近来更新ADT后就重新开启Eclipse这个问题就会莫名发生。只能强制退出Eclipse,再次打开也如故。
目前解决办法是强退后手动清除cache:

1
2
3
cd ~/.android
rm -rf ./cache
rm -r ./ddms.cfg

然后重启Eclipse

另外,stackoverflow上也有人讨论这个问题:http://stackoverflow.com/questions/13489141/eclipse-hangs-at-the-android-sdk-content-loader,提出了许多办法,大多是清除用户数据之类,一一尝试总有一款适合你~~

Android 音频焦点(Audio Focus)

引子

说 Audio Focus 前先说个很简单需求:来电暂停正在播放的音乐,电话结束时恢复播放。

看到这个需求,第一反应肯定是:监听用户来电状态,作相应操作。这里不多做介绍,这样做有个不好的地方就是需要隐私权限!这样做一点也不优雅

后来搜索时看到一篇分析文章:Android来电时停止音乐播放的流程(顺便说一嘴,这篇转载居然不注明出处!!)。文章里的分析很明确的指出,系统在框架层就很好的帮我们处理了这个需求,问题是如何将音乐交给系统框架来处理呢?

音频焦点

问题的解决方法就是:请求系统的音频焦点Request the Audio Focus)。

如果英文还行,强烈建议请看官方的原文:Managing Audio Playback,里面介绍的很清楚。以下为简单概述。

官方文档指出Android 在处理音频播放是分了多个“音频流”的,如音乐流、音效流、电话声音流等,使控制音量时可以互不干涉。多数情况下我们播放音乐都是使用 STREAM_MUSIC 音频流。

另外,系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何协调,为了避免同时播放音乐,Android 系统使用音频焦点来进行统一管理,即只有获得了音频焦点的应用程序才可以播放音乐。

那么,播放音频应该这样来做:

  1. 获取音频焦点 requestAudioFocus
  2. 获取成功后,开始播放音频
  3. 处理音频焦点的丢失和“DUCK”
  4. 播放完毕后取消焦点

如此便可以完美的解决引子里的需求。

TextView Get Line Count return 0?

想给TextView加上个“展开/收起”的功能,思路是这样的,给TextView限制maxLine为4行,当getLineCount() >=4时,显示“展开”按钮。
但是无论怎么在textView#setText 之前还是之后去getLineCount()都返回的“0”…..
仔细“领会”了一番doc:

Return the number of lines of text, or 0 if the internal Layout has not been built.

原因应该是内部的布局还没有绘制完毕,我就去getLineCount()了,当然会返回0咯。

正确的get姿势应该如下

1
2
3
4
5
6
7
8
9
10
11
mTextView.setText("large text");
mTextView.post(new Runnable() {
@Override
public void run() {
int lineCount = mTextView.getLineCount();
if(lineCount >=4)
showDetailButton();
}
});

包名修改老用户迁移记

不知名的原因,原来的包被下架了,只能改包名卷土重来了。
改包名容易,但原来的2k+活跃用户咋办。。。

后面想到个辙,做一个原包名的空壳子APK,他做下列动作:

  1. 备份/data/data/<pkg>/ 下的数据到SD卡
  2. 安装新包名版本,该apk会检测SD卡备份的数据,将其收入囊中
  3. 安装完毕(无论用户是否取消,因为这时,数据一定是备份了的),卸载自己
  4. 成功迁移。

但是在付诸行动的时候,在第一步犯了个致命错误!

ViewPager: Can't change tag of fragment

问题描述:

在应用里用到了ViewPagerFragmentPagerAdapter 实现多页(>20页)滑动,每一页都是一个Fragment,给ViewPager注册一个OnPageChangeListener,当知道某一个Fragment被选中时,开始调用其刷新数据的方法。

并有一目录,点击某个条目时,调用 ViewPagersetCurrentItem(position) ,页面跳转到指定页,但是此时奇怪的是注册的 OnPageChangeListener#onPageSelected 居然没有被调用,表现为选中的页面没有刷新数据。

这时候往右侧滑动几页没问题,再往左侧滑动几页就一定会报出异常:

java.lang.IllegalStateException: Can’t change tag of fragment PageFragment{42ef29a8 #14 id=0x7f060052
android:switcher:2131099730:22}: was android:switcher:2131099730:22 now android:switcher:2131099730:17

分析原因:

经过一番排查,发现问题出现的原因在于我的FragmentPagerAdapter 内部维护的List集合造成的,因为我想自己来控制一下Fragment内部数据的销毁,并且使Fragment得到重用。

但是,在往List集合中,add(fragment)的时候,没有注意FragmentPagerAdapter#getItem(position)传入的位置和实例化fragment所存放在List中的位置!

[转]慎用File.renameTo(file)

本文转自:http://xiaoych.iteye.com/blog/149328

以前我一直以为File#renameTo(File)方法与OS下面的 move/mv 命令是相同的,可以达到改名、移动文件的目的。不过后来经常发现问题:File#renameTo(File)方法会返回失败(false),文件没有移动,又查不出原因,再后来干脆弃用该方法,自己实现一个copy方法,问题倒是再也没有出现过。

昨天老板同学又遇到这个问题,File#renameTo(File)方法在windows下面工作的好好的,在linux下偶尔又失灵了。回到家我扫了一遍JDK中File#renameTo(File)方法的源代码,发现它调用的是一个本地的方法(native method),无法再跟踪下去。网上有人说该方法在window下是正常的,在linux下面是不正常的。这个很难说通,SUN不可能搞出这种平台不一致的代码出来啊。

后面在SUN的官方论坛上看到有人提到这个问题“works on windows, don’t work on linux”,后面有人回复说是“file systems”不一样。究竟怎么不一样呢?还是没有想出来…

后面在一个论坛里面发现了某人关于这个问题的阐述:

>

In the Unix’esque O/S’s you cannot renameTo() across file systems. This behavior is different than the Unix “mv” command. When crossing file systems mv does a copy and delete which is what you’ll have to do if this is the case.

The same thing would happen on Windows if you tried to renameTo a different drive, i.e. C: -> D:

终于明白咯。

做个实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
File sourceFile = new File("c:/test.txt");
File targetFile1 = new File("e:/test.txt");
File targetFile2 = new File("d:/test.txt");
System.out.println("source file is exist? " + sourceFile.exists()
+ ", source file => " + sourceFile);
System.out.println(targetFile1 + " is exist? " + targetFile1.exists());
System.out.println("rename to " + targetFile1 + " => "
+ sourceFile.renameTo(targetFile1));
System.out.println("source file is exist? " + sourceFile.exists()
+ ", source file => " + sourceFile);
System.out.println(targetFile2 + " is exist? " + targetFile2.exists());
System.out.println("rename to " + targetFile2 + " => "
+ sourceFile.renameTo(targetFile2));

结果:

source file is exist? true, source file => c:\test.txt  
e:\test.txt is exist? false  
rename to e:\test.txt => false  
source file is exist? true, source file => c:\test.txt  
d:\test.txt is exist? false  
rename to d:\test.txt => true  

注意看结果,从C盘到E盘失败了,从C盘到D盘成功了。
因为我的电脑C、D两个盘是NTFS格式的,而E盘是FAT32格式的。所以从C到E就是上面文章所说的”file systems”不一样。从C到D由于同是NTFS分区,所以不存在这个问题,当然就成功了。

果然是不能把File#renameTo(File)当作move方法使用。

可以考虑使用apache组织的commons-io包里面的FileUtils#copyFile(File,File)和FileUtils#copyFileToDirectory(File,File)方法实现copy的效果。至于删除嘛,我想如果要求不是那么精确,可以调用File#deleteOnExit()方法,在虚拟机终止的时候,删除掉这个目录或文件。

BTW:File是文件和目录路径名的抽象表示形式,所以有可能是目录,千万小心。

Android ant builder

https://github.com/yrom/Android-Ant-Builder/
使用Ant 实现批量打包Android应用

基于这篇文章的一个实现 http://blog.csdn.net/t12x3456/article/details/7957117

如何使用

配置Ant环境

从Ant官网下载所需ant的jar包

这里以windows配置为例:

Windows下ANT用到的环境变量主要有2个,ANT_HOMEPATH

设置ANT_HOME指向ant的安装目录。

设置方法:
ANT_HOME = D:/apache_ant_1.9.1

%ANT_HOME%/bin; %ANT_HOME%/lib添加到环境变量的path中。

设置方法:
PATH = %PATH%; %ANT_HOME%/bin; %ANT_HOME%/lib

配置ant builder

  1. 修改config.properties的各项value
    具体含义见Main.java
  2. market.txt里保存需要打包的市场标识
    “#”表示注释

配置需要打包的工程

初始化工作:(一般只需要操作初始化一次就够了)

  1. 运行android update project -p <你的工程目录>可以自动生成ant 打包所需的 build.xml (包括依赖lib工程也需要执行一次)

  2. 修改ant.properties中签名文件的路径和密码(如果没有请自行添加)

    1
    2
    3
    4
    key.store=D:\\android\\mykeystore
    key.store.password=123456
    key.alias=mykey
    key.alias.password=123456

*具体应用可以参见https://github.com/yrom/acfunm

执行!

执行 Main.java即可
也可以编译成jar包,方便使用,这里就不做详细介绍了。

你也可以做相应的修改及完善

欢迎Fork,Pull request :D