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 看起来跟旋转屏幕之前的没什么两样,但它们是两个实例)

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

Android Studio

Android Studio v0.1

Download

Google I/O大会上,发布了Android Studio的预览版。
看发布会上演示时,着实让我惊艳了一番,迫不及待的下载下来,安装后却发现,新建工程时,一直卡在下载某个文件的对话框上。。。人品不行吗?
Android Studio v0.1

下文来源: 网易科技报道

(Android studio)首先解决的一个问题是多分辨率。Android设备拥有大量不同尺寸的屏幕和分辨率,根据新的Studio,开发者可以很方便的调整在各个分辨率设备上的应用。

同时Studio还解决语言问题,多语言版本、支持翻译都让开发者更适应全球开发环境。Studio还提供收入记录功能。

最大的改变在于Beta测试的功能。Studio提供了Beta Testing,可以让开发者很方便试运行。

想想就很给力,希望快些发布正式版~~

给TextView中的文字加上链接

项目上需要给TextView中特定格式文本加上”超链接“,让用户可以点击链接后,可以开启关心该uri的Activity。

如给”abc12345“加上链接,使其点击后,开启Activity,该Activity意图过滤器关心的uri scheme为”abc://“。

利用正则表达式,匹配出”abc12345“。
利用Linkify类给文字加上链接到”abc://abc12345”:

1
2
3
4
5
public void addLink(TextView text){
String localDesc = description;
text.setText(localDesc);
Linkify.addLinks(text, Pattern.compile("(ac\\d{5,})", Pattern.CASE_INSENSITIVE),"abc://");
}

这样用户点击后,内部会startActivity(intent);
该intent的data为 “abc://abc12345”,这样注册了该scheme IntentFilter的Activity就会被开启了。

当然,也可以给符合http协议的url自动加上超链接:

1
2
Pattern http = Pattern.compile("(http://(?:[a-z0-9.-]+[.][a-z]{2,}+(?::[0-9]+)?)(?:/\\S*)?)",Pattern.CASE_INSENSITIVE);
Linkify.addLinks(text,http,"http://");

用户点击后,开启的Intent 包含data为 “http://…”,这样就会开启浏览器,浏览该网址了。

Android - Parcel & Parcelable

对于Parcel的理解:

在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然要求使用性能更出色的对象传输方式。显然,JAVA的Serialize利用外部存储设备被认为是低效的,
可能也无法完美匹配Binder机制。在这样的环境下,Parcel被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。

为了便于ipc之间传递的数据的操作,binder引入了parcel的概念。parcel可以想成快递公司的包装箱,需要传递的各种类型的数据都被打包进parcel类,binder负责传递parcel对象,接收端则从parcel解出数据。这样的机制即减少了各种数据类型对传递的复杂性,又可以通过增加打包/解包parcel的数据类型,轻易实现扩展。
parcel已经支持容纳基本数据类型和一些复合数据类型。

下面内容转自 dairyman的专栏:http://blog.csdn.net/dairyman000/article/details/7247619

Parcel 在英文中有两个意思,其一是名词,为包裹,小包的意思; 其二为动词,意为打包,扎包。邮寄快递中的包裹也用的是这个词。
Android采用这个词来表示封装消息数据。这个是通过IBinder通信的消息的载体。需要明确的是Parcel用来存放数据的是内存(RAM),而不是永久性介质(Nand等)。

Parcelable

定义了将数据写入Parcel,和从Parcel中读出的接口。
一个实体(用类来表示),如果需要封装到消息中去,就必须实现这一接口,实现了这一接口,该实体就成为“可打包的”了。

接口的定义如下:

Parcelable.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Parcelable {  
//内容描述接口,基本不用管
public int describeContents();
//写入接口函数,打包
public void writeToParcel(Parcel dest, int flags);
//读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。
//因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入。
//为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例。
public interface Creator<T> {
public T createFromParcel(Parcel source);
public T[] newArray(int size);
}
}

在实现Parcelable的实现中,规定了必须定义一个静态成员, 初始化为嵌入接口的实现类。

1
2
public static Parcel.Creator<DrievedClassName>  CREATOR
= new Parcel.Creator<DrievedClassName>();