每次换手机,把旧手机的数据迁移到新手机就是个很麻烦的事情,幸好最近华为的“手机克隆”APP越来越强大,居然能够把微信的聊天记录包括图片原封不动地迁移到新手机上,以前用微信自带的聊天记录转移功能只能转移文字信息,图片视频全部丢失,不知道现在的怎么样。手机克隆还能把SD卡的内容也转移过来,基本满足了需要。
但是要把手机上的东西传到电脑就没那么简单了,现在已经没有了以前的大容量存储模式,只能选择MTP模式,这种模式其实不是一个完整的文件系统,有很多限制,所以一些传统的软件读取不到,例如FastCopy是用不了的,用Windows自带的文件管理器来复制,开始计算时间就要等很久,中间出了个错就前功尽弃;还有通过手机上的APP访问电脑共享的方式,在手机上复制也可以,但是同样会莫名其妙卡死,FTP同理,折腾了好久,还是觉得自己动手比较好。
MTP协议在维基百科里解释得比较清楚:
https://en.wikipedia.org/wiki/Media_Transfer_Protocol ,简单点说就是:
1、不是以块设备的形式访问,跟U盘不同;
2、只能单线程访问,不能同时进行多个操作,只能一个接一个;
3、控制权在设备上,对外展示的内容由设备决定;
4、默认不能直接对文件进行部分修改,只能复制过来修改完再复制回去,但Android对协议做了扩展,能够修改部分文件内容;
5、在Linux上有些软件能够把它挂载为文件系统,这样其他软件就能像访问普通文件系统一样访问了,但是Windows下似乎没有。
不过有人开发了一个在Windows下通过JNI实现的Java库jmtp,项目托管在Google Code,被墙了,但是GitHub有人fork了一个,可以下载下来,我下载的是
https://github.com/reindahl/jmtp
里面包含了C++的代码和Java的代码,以及两个已经编译好的dll文件,分别用于Win32和Win64,把其中一个dll文件放在工程目录下,再把Java源代码加入工程中即可使用,文档比较简陋,但是看test目录下的MtpTest.java,基本可以摸到如何使用了,这个协议比较简单,其实没什么功能,我要的只是把文件复制到电脑上。
根据MtpTest.java,稍微修改一下,做个递归复制即可把手机上的所有文件复制到电脑上:
package test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.rmi.server.SocketSecurityException;
import java.util.ArrayList;
import jmtp.PortableDevice;
import jmtp.PortableDeviceFolderObject;
import jmtp.PortableDeviceManager;
import jmtp.PortableDeviceObject;
import jmtp.PortableDeviceStorageObject;
public class TestApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList<PortableDeviceStorageObject> devices = new ArrayList<>();
PortableDeviceManager manager = new PortableDeviceManager();
for (PortableDevice device : manager) {
System.out.println(device);
device.open();
// Iterate over deviceObjects
for (PortableDeviceObject object : device.getRootObjects()) {
String storageName=object.getName();
System.out.println(storageName);
// If the object is a storage object
if (object instanceof PortableDeviceStorageObject) {
PortableDeviceStorageObject storage = (PortableDeviceStorageObject) object;
System.out.println(storage.getChildObjects().length);
for (PortableDeviceObject child : storage.getChildObjects()) {
copyall(child,"E:\\手机备份\\"+object.getName());
}
}
}
device.close();
System.out.println(size);
}
}
public static void copyall(PortableDeviceObject obj,String path) {
if(obj instanceof PortableDeviceFolderObject) {
String objName=obj.getName();
if(objName.contains(":")) {
objName=objName.replace(':', ':');
}
String newPath = path+"\\"+objName;
System.out.println("创建文件夹:"+newPath);
File file = new File(newPath);
if(!file.exists()) {
file.mkdirs();
}
for(PortableDeviceObject subObj:((PortableDeviceFolderObject) obj).getChildObjects()) {
copyall(subObj,newPath);
}
}
else {
if(obj.getName().contains(":"))
return;
System.out.println("开始复制文件到:"+path+"\\"+obj.getName());
File file = new File(path);
obj.copy(file.toPath());
System.out.println("文件复制完成!");
}
}
}
其中发现有点问题:
1、Android设备文件名里是可以包含冒号(:)的,但Windows是不可以的,所以复制到这些文件的时候会有问题,于是遇到目录名这样就把它改为中文的冒号(:),但是遇到文件名这样就不行了,因为这个库的copy函数只需要指定目标目录,不需要指定目标文件名,所以这些文件只能放弃;
2、Android手机的MTP协议是由“媒体存储”这个系统APP控制的,有时候手机上可以看到的文件,通过MTP访问却怎么也看不到,重启手机也不行,应该就是这个APP没有更新数据,需要把它的系统数据清除掉,等它重建完重新访问就可以看到了,不过这个重建时间非常长,可以查看它数据占用的空间,刚清除之后会发现它占用的空间会不断增长,到了不增长的时候就是重建完了,就可以正常访问了;
3、这个库有时候还有点bug,有一次发现它读取到的文件和文件夹都没有了最后一个.后面的部分,所以总是卡住,重新插拔一下手机数据线又没问题了;
4、为了避免复制了半天结果发现不完整,又要重来,最好在复制前先统计一下文件大小,看看跟手机上看到的占用存储空间是不是一致,对于MTP设备上的文件,可以通过getSize函数得到大小,把上面复制操作改为大小累加即可,速度比复制快一些,不过由于小文件太多,也不会快很多。
把手机里的文件复制到电脑后,通过一些简单的分析,发现有很多其实是垃圾来的,也可以为手机空间清理提供参考,因为在电脑上分析起来比在手机上方便一些。例如一些视频APP的缓存,居然超过1G,占用了宝贵的内部存储空间,之前一直都没发现,通过电脑里的按文件大小搜索才发现。