[转]慎用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销毁时将持有的数据清空、停止异步任务。

根据Android系统版本禁用/启用某些Activity

在开发中,需要用到Android新版本的特性,而又要适配旧版本。如何让Activity低版本中不显示,而高版本无影响呢?

在查看APIDemos源码时,看到AndroidManifest.xml中有这样一段

AndroidManifest.xml
1
2
3
4
5
6
<activity android:name=".animation.AnimationLoading"
android:enabled="@bool/atLeastHoneycomb">
<intent-filter>
...
</intent-filter>
</activity>

android:enabled这个属性是控制Activity是否能被实例化的。
@bool/atLeastHoneycomb是指values/bools.xml中的atLeastHoneycomb属性的值。
该文件定义了许多与平台相关的atLeastXxx属性,默认值都是false

在APIDemos/src/res/ 目录下有许多的values-vXX目录,各自下面都有bools.xml
values-v11/bools.xml中可以看到

bools.xml
1
2
3
4
<resources>
<!-- True if running under Honeycomb or later. -->
<bool name="atLeastHoneycomb">true</bool>
</resources>

即,.animation.AnimationLoading这个Activity在v11版本以上系统可以被实例化,以下则不能。
如此便做到了让应用更好的适配各个版本。

延伸:

activity-alias
为某个Activity起别名,重复使用同一个Activity,同样可以做到平台相关的一些处理:
比如有Activity需要在Launcher上创建入口,但又不想在低版本上这样做。

BuildConfig.DEBUG 的妙用

ADT(R17)会自动生成一个名称为BuildConfig的类,该类包含一个DEBUG 常量,该常量会根据当前项目的Build类型自动设置值。
可以通过(BuildConfig.DEBUG) 常量来编写只在Debug模式下运行的代码。
如果有些代码不想在发布后执行,就可以使用该功能。

Added a feature that allows you to run some code only in debug mode.
Builds now generate a class called BuildConfig containing a DEBUGconstant that is automatically set according to your build type. You can check the (BuildConfig.DEBUG) constant in your code to run debug-only functions.

比如调试日志,不想在软件发布后被其他开发者看到,过去的方式是设置一个全局变量,标记软件为DEBUG模式还是其他模式。

1
2
3
4
5
public static boolean DEBUG = true;  
//...
if(DEBUG==true){
Log.d(TAG,"output something");
}

这样打包发布之前还要修改DEBUG变量的值,有时候不记得或者变量到处都有,重新修改、编译、发布,费时费力。

有了BuildConfig.DEBUG之后,代码变成了

1
2
3
if (BuildConfig.DEBUG) {  
Log.d(TAG, "output something");
}

在编码期,编译发布前,BuildConfig.DEBUG的值自动为true
需要打包时,先禁用 “Build Automatically”, “Clean”工程目录,再通过 “Android Tools -> Export Signed Application Package“ 编译打包,BuildConfig.DEBUG的值会被改为false。
开发者自己不用修改其他东西了。

流程如下[1]

  1. Project -> Build Automatically
  2. Project -> Clean
  3. Project -> Build
  4. Android Tools -> Export Android application

从此,Logcat清爽了许多。

[1]-When does ADT set BuildConfig.DEBUG to false?

Android - Service

Android中的服务与windows中的服务非常类似——没有用户操作界面、长时间在后台运行而不易被用户所知。

应用程序可以开启某些服务在后台运行,即使用户切换到其他应用程序也不会被停止。同时,应用程序可以bind一个服务与之交互,甚至可以执行IPC,例如:网络处理、音乐播放、文件输入输出或者是与内容提供者交互,都可以放在后台执行。

服务的两个运行方式

应用的组件(如Activity)可以通过下面两个方式运行服务:

  1. startService():开启服务(started)。
    即使应用被关闭,服务也不会被停止。这种开启方式,没有返回值,不能和应用交互,如下载和上传文件到网络,注意的是:服务应该在业务执行完毕后自我停止。
  2. bindService():绑定服务(bound)。
    服务被绑定后可以与应用交互数据。当应用被关闭后,服务应立即被解绑(unbind)。一个服务可以被多个应用绑定,当这些应用都解绑了服务,服务将被销毁(destoryed)。

Tips:服务可以同时被这两个方式开启。但要注意的是:如果被绑定,则服务只能在解绑后才能被停止。
一般两者混用的调用顺序为:startService()->bindService()->unbindService()->stopService()。

Service的生命周期

  1. onStartCommand():当其他组件startService()来启动服务时,系统调用这个方法。一经执行,服务开始运行在后台。当服务业务执行完毕后应调用stopSelf()自我停止或者其他组件来调用stopService()停止服务。如果这个服务只是提供内容绑定,则不应该去实现这个方法。
  2. onBind():服务通过bindService()方式启动,系统调用此方法。应该提供一个接口给应用,使之能回调服务中的方法(接口其实就服务的代理),方法应返回一个实现了该接口的IBinder对象。如果不想让应用绑定则应该返回null。
  3. onCreate():系统仅在服务第一次被创建时调用。之前会执行上面两个方法
  4. onUnbind():在被解绑时调用。如果服务可以被重新绑定(onRebind()),应返回true。
  5. onRebind():在被解绑(onUnbind()返回true)后又被重新bindService()启动,则会调用此方法。
  6. onDestory():系统会在服务许久不使用或在销毁时调用。可以实现此方法,在服务销毁前清除使用过的资源。

Tips:

  1. 服务的生命周期方法不需要像Activity的生命周期方法那样,在实现方法体中需要显式调用其父类的方法来维护生命周期。
  2. 多次startService()开启服务不会多次调用onCreate()但会多次调用onStart()
  3. 服务在所有绑定组件unbindService()解绑时会被系统销毁,需要自我停止。

Android - Content Provider

ContentProvider在android中的作用是对外共享应用的私有数据,也就是说可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对应用中的数据进行添删改查。

关于数据共享,前面的文件操作模式中知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE或Context.MODE_WORLD_WRITEABLE同样也可以对外共享数据。

那么,为何还要使用ContentProvider 对外共享数据呢?
如果采用文件操作模式对外共享数据,数据的访问方式会因数据存储的方式不同而不同,导致数据的访问方式无法统一,
如:采用xml文件对外共享数据,需要进行xml解析才能读取数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读取数据等等。

使用ContentProvider对外共享数据的好处是统一了数据的访问方式

成为ContentProvider

当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:

ContentProvider - PersonContentProvider.java
1
2
3
4
5
6
7
8
public class PersonContentProvider extends ContentProvider{
public boolean onCreate(){}
public Uri insert(Uri uri, ContentValues values){}
public int delete(Uri uri, String selection, String[] selectionArgs){}
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs){}
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){}
public String getType(Uri uri){}
}

第二步需要在AndroidManifest.xml 的<provider>节点对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider,ContentProvider 采用了authority(主机名/域名)对它进行唯一标识(详见Content URIs)

Manifest - AndroidManifest.xml
1
2
3
4
5
<manifest .... >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name=".PersonContentProvider" android:authorities="net.yrom.providers.personprovider"/>
</application>
</manifest>

Tips:Content Provider在<provider>节点中也可以声明一些访问的权限,当访问者没有申请到权限是不能访问provider的。

ContentProvider类主要方法

  • boolean onCreate()
    该方法在ContentProvider创建后就会被调用, Android开机后, ContentProvider在其它应用第一次访问它时才会被创建。
  • Uri insert(Uri uri, ContentValues values)
    该方法用于供外部应用往ContentProvider添加数据。
  • int delete(Uri uri, String selection, String[] selectionArgs)
    该方法用于供外部应用从ContentProvider删除数据。
  • int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
    该方法用于供外部应用更新ContentProvider中的数据。
  • Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
    该方法用于供外部应用从ContentProvider中获取数据。
  • String getType(Uri uri)
    该方法用于返回当前Url所代表数据的MIME类型。

如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头。
例如:要得到所有person记录的Uri为content://net.yrom.provider.personprovider/person,那么返回的MIME类型字符串应该为:“vnd.android.cursor.dir/person”。

如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头。
例如:得到id为10的person记录,Uri为content://net.yrom.provider.personprovider/person/10,那么返回的MIME类型字符串应该为:“vnd.android.cursor.item/person”。