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>();

让多个Fragment 切换时不重新实例化

在项目中需要进行Fragment的切换,一直都是用replace()方法来替换Fragment:

1
2
3
4
5
6
7
8
9
public void switchContent(Fragment fragment) {
if(mContent != fragment) {
mContent = fragment;
mFragmentMan.beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, R.anim.slide_out)
.replace(R.id.content_frame, fragment) // 替换Fragment,实现切换
.commit();
}
}

但是,这样会有一个问题:
每次切换的时候,Fragment都会重新实例化,重新加载一边数据,这样非常消耗性能和用户的数据流量。

就想,如何让多个Fragment彼此切换时不重新实例化?

翻看了Android官方Doc,和一些组件的源代码,发现,replace()这个方法只是在上一个Fragment不再需要时采用的简便方法。

正确的切换方式是add(),切换时hide()add()另一个Fragment;再次切换时,只需hide()当前,show()另一个。
这样就能做到多个Fragment切换不重新实例化:

1
2
3
4
5
6
7
8
9
10
11
12
public void switchContent(Fragment from, Fragment to) {
if (mContent != to) {
mContent = to;
FragmentTransaction transaction = mFragmentMan.beginTransaction().setCustomAnimations(
android.R.anim.fade_in, R.anim.slide_out);
if (!to.isAdded()) { // 先判断是否被add过
transaction.hide(from).add(R.id.content_frame, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
} else {
transaction.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
}
}
}

————Edited 2015.2.7————-

问题一:保存UI与数据的内存消耗

上面所述为避免重新实例化而带来的“重新加载一边数据”、“消耗数据流量”,其实是这个Fragment不够“纯粹”。

Fragment应该分为UI FragmentHeadless Fragment

前者是指一般的定义了UI的Fragment,后者则是无UI的Fragment,即在onCreateView()中返回的是null。将与UI处理无关的异步任务都可以放到后者中,而且一般地都会在onCreate()中加上setRetainInstance(true),故而可以在横竖屏切换时不被重新创建和重复执行异步任务。

这样做了之后,便可以不用管UI Fragment的重新创建与否了,因为数据和异步任务都在无UI的Fragment中,再通过Activity 的 FragmentManager 交互即可。

只需记得在Headless Fragment销毁时将持有的数据清空、停止异步任务。