Gradle 添加上传 APK 到FIR.im支持

!DEPRECATED 由于FIR网站的更新,这篇文章的脚本已经失效了!新的API的利用脚本正在捣鼓中…

关于FIR.im就不做过多介绍了,本文主要是为了解决通过 Android Studio 或者说 Android Gradle 构建工具快速上传 APK 到 FIR 上。

FIR提供了上传APK的API,下面就是使用这个API的脚本:

Gradle 修改 Maven 仓库地址

近来迁移了一些项目到Android Studio,采用Gradle构建确实比原来的Ant方便许多。但是编译时下载依赖的网速又着实令人蛋疼不已。

如果能切换到国内的Maven镜像仓库,如开源中国的Maven库(已停止维护),或阿里云的Maven服务,又或者是换成自建的Maven私服,那想必是极好的。

一个简单的办法,修改项目根目录下的build.gradle,将jcenter()或者mavenCentral()替换掉即可:

1
2
3
4
5
allprojects {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
}
}

但是架不住项目多,难不成每个都改一遍么?

自然是有省事的办法,将下面这段Copy到名为init.gradle文件中,并保存到 USER_HOME/.gradle/文件夹下即可。

Apktool 解包资源引用偏移的问题

Apktool(v2.0.0rc3) 解由最新的build-tools(v21.x) 所编译的Apk,如果其引用了Android Lollipop的Material主题,会出现资源引用偏移的情况。
values-v21文件夹下的styles.xml中的主题原来是:

<style name="AppBaseTheme" parent="@android:style/Theme.Material.Light">

解包后却变成

<style name="AppBaseTheme" parent="@android:style/Widget.Holo.DateTimePicker">

##原因
Apktool decode 某个Apk首先需要加载 packageandroid (其package id一般为0x01[1]) 的资源映射表(Resource Table),这就是第一次运行Apktool会安装内置资源包resources.arscxxx/apktool/framework/1.apk的原因[2]

可想而知,如果这个资源包没有及时升级,自然就会导致资源引用偏移甚至解包失败:

Exception in thread "main" brut.androlib.err.UndefinedResObject:resource spec: 0x01...

DialogFragment的Listener在屏幕旋转后被重置的问题

问题

DialogFragment 在 Activity 中创建后通过 show(fm, tag),弹出 Dialog,如下面的代码所示:

1
2
3
MyDialogFragment dialog = new MyDialogFragment(); // 一个自定义的 DialogFragment
dialog.setButtonListener(aListener); // 一个自定义方法,设置一个监听(如某个按钮的回调)
dialog.show(getFragmentManager(), "tag"); // 弹出对话框

点击按钮可以回调 Activity 的 aListener,但是,如果在对话框存在的情况下旋转屏幕,再怎么点按钮aLisenter也不回调了?!

原因

只要理解了Fragment 和 Activity 生命周期就会知道原因其实很简单:

  1. 旋转屏幕时,Activity将会被重新创建。
  2. Activity“临终”前会在onSaveInstanceState()中保存 DialogFragment的状态(FragmentManagerState);
  3. “复活”后的Activity,在onCreate()中会根据savedInstanceState所给予的FragmentManagerState自动重新实例化DialogFragment,并且 show()出 dialog

流程为:

旋转屏幕-->-Activity.onSaveInstanceState()-->-Activity.onCreate()-->- DialogFragment.show()--|

这个时候的DialogFragment所持有的aListener引用当然也不复存在,再点按钮自然也不会收到回调(虽然这个 dialog 看起来跟旋转屏幕之前的没什么两样,但它们是两个实例)

简述用 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. 成功迁移。

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