前言
大学毕业后来到公司的第一件工作就是通过U盘插入装有安卓系统的机器,通过U盘向机器设置设备码(下称SN)。一开始我的思路就是通过InStream先取到U盘里文件的数据,然后通过OutStream修改文件,然后把读出来的SN通过封装好的api接口发到机器上就完事。思路大体是对的,但是中间过程折磨了我这个新手好几天,接下来我将对其中的每一个环节进行分析。
一、十六进制dat文件解析
如果大家也有做到关于读取字节文件dat的,一定一定一定要问你的负责人要到关于dat文件的格式说明!!!不然会超级麻烦。话不多说,先展示一下我的dat文件的格式:
上述图片就是我用到的sn.dat文件。这里就不提我的摸索痛苦经历了,直接上结论。
首先最左边一列(“空格”前那一串数字)并不是我们会读取到的数据,我的理解是一种序号并且在你读写文件的时候并不会影响到,相当于是隐形的,简单来说就是可以不用管,这东西给你是方便看的。当然空格也是,实际上并没有空格,都是用来方便看的。然后最右边呢一串像乱码一样的东西也不用管,我的负责人跟我说是类似注解说明一样的东西,所以这个dat文件实际对我们有用的部分就是中间这一大块数据。
我这个dat文件是十六进制的形式,我们把每一行分为4组,每一组8个数。拿一行的第一组为例:803E0000 我先说结论,每两个数表示一个十六进制数,每4个十六进制数也就是一组代表一个数,我这里的数据采用的是小端结构,所以读出来就是"0x00、0x00、0x3E、0x80",换成十进制就是16000。解释一下,一个int型数据有4个字节,4byte就是32位,一个十六进制的数据(0x3E 这样)是8位,所以4个十六进制数就代表了一个int型的数。然后关于什么是小端结构,小端结构说白了就是低位写在前面,我们平时写的数据就是相反的大端结构,是高位写在前面。在硬件底层一般数据传输用的都是小端结构,通讯协议用的是大端结构。具体的详解可以百度大端、小端模式。
二、十六进制dat文件读操作
在读dat文件之前,我先说明一下我的文件格式,前4个字节存的是读到第几个SN号,9-12个字节存放的是SN号总数。然后从第六行开始,每8个字节为一组,前4个字节是标志位,0代表SN没用过,1代表已使用过;后4个字节才表示SN号。
1.读取手机中的dat文件数据
这里与一般读写文件的区别在于我们是需要读写字节文件,代码如下。
public static void changeDat(File file) {
FileInputStream inStream = null;
FileOutputStream outStream = null;
SNFileStream snFileStream = new SNFileStream();
int dataPos = 0;//SN码到的使用位置
int dataCount = 0;//SN码总数
byte[] bytes = new byte[220 * 1024];//获取sn.dat内容
int SNFlag = 0;//标志位
int SNValue = 0;//设备号
String StrSNValue = "";
File newFile = new File("/mnt/shared/Pictures/sn2.dat");
try {
inStream = new FileInputStream(file);
outStream = new FileOutputStream(newFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//getChannel()该方法的返回类型为FileChannel,它返回与此流连接的FileChannel。
ReadableByteChannel inCh = inStream.getChannel();
{
//分配直接字符缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(16);
//ByteOrder.LITTLE_ENDIAN表示小端模式
buffer.order(ByteOrder.LITTLE_ENDIAN);
int num = 0;
while (true) {
try {
if (inCh.read(buffer) == -1) break;
} catch (IOException e) {
e.printStackTrace();
}
buffer.flip();
bytes[num] = buffer.get();
num++;
buffer.compact();
}
//关闭FileInPutStream
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.修改dat文件
把dat文件里的内容全部读出来存入byte数组后,拿出需要的数据并将需要修改的内容在byte数组中改,然后重新存入一个dat文件替代原来的文件,代码如下(接上)。
//获取索引 4个byte代表一个32位的十六进制数
byte[] mid = new byte[4]; //取32位十六进制数
for (int i = 0; i < 4; i++) {
mid[i] = bytes[i];
}
dataPos = snFileStream.byte2int(mid);
Log.d("dataPos", String.valueOf(dataPos));
//更新dataPos
int updateDataPos = dataPos + 2;
mid = snFileStream.int2byte(updateDataPos);
for (int i = 0; i < 4; i++) {
bytes[i] = mid[i];
}
//获取总数
for (int i = 0; i < 4; i++) {
mid[i] = bytes[i + 8];
}
dataCount = snFileStream.byte2int(mid);
Log.d("dataCount", String.valueOf(dataCount));
//获取SN码
int SNValuePos = 4 + 8 * (dataPos - 1) + 80;
for (int i = 0; i < 4; i++) {
mid[i] = bytes[i + SNValuePos];
}
for (int i = 3; i >= 0; i++) {
StrSNValue = StrSNValue.concat(DataTypeConvert.oneByte2TwoHexString(mid[i]));
}
Log.d("=-=-=-=-=-=-=-", StrSNValue);
SNValue = snFileStream.byte2int(mid);
Log.d("SNValue", String.valueOf(SNValue));
//更新SN的标志位
bytes[SNValuePos - 1] = 0x01;
//把更新后的所有数据写入新dat文件中
try {
int length = bytes.length;
Log.d("snLength", String.valueOf(length));
outStream.write(bytes, 0, length);
outStream.flush();
outStream.close();
} catch (IOException e) {
e.printStackTrace();
}
Log.d("sn2.dat_Length", String.valueOf(newFile.length()));
Log.d("sn1.dat_Length", String.valueOf(file.length()));
//更新文件
boolean successA = file.delete();
if (successA) {
Log.d("删除结果", "成功!!!");
} else {
Log.d("删除结果", "失败!!!");
}
File newFileName = new File("/mnt/shared/Pictures/sn.dat");
boolean successB = newFile.renameTo(newFileName);
if (successB)
Log.d("重命名结果", "Success!!!");
else
Log.d("重命名结果", "Fail!!!");
}
这段代码里有用到两个转换函数,是用来把byte和int互相转换,两个函数如下。
public static byte[] int2byte(int intValue) {
byte[] b = new byte[4];
for (int i = 0; i < 4; i++) {
b[i] = (byte) (intValue >> 8 * i & 0xFF);//低位在前
}
return b;
}
public static int byte2int(byte[] b) {
int intValue = 0;
byte[] tempArray = new byte[4];
for (int i = 0; i < b.length; i++) {
tempArray[i] = b[i];
}
for (int i = 3; i >= 0; i--) {
intValue |= (tempArray[i] & 0xFF) << (8 * i);
}
return intValue;
}
以上就是在手机本地读取和存储dat文件的方法,接下来介绍一下U盘的读取。
3.获取U盘权限
首先在读取U盘文件之前,需要申请U盘权限(前面也别忘记写上权限,一并写这里了)
<!--表示支持usb设备-->
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
<uses-permission android:name="android.hardware.usb.host" android:required="false" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
在声明权限之后还需要动态获取U盘(USB设备的)的权限,然后用BroadcastReceiver根据接收到的不同广播去分别处理对应的事件,具体代码如下。
//注册广播,监听otg插拔事件。
public void registerReceiver() {
IntentFilter usbDeviceStateFilter = new IntentFilter();
//对应的USB设备插入的广播
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
//对应的USB设备拔出的广播
usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, usbDeviceStateFilter);
//注册监听自定义广播
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
}
//根据action,去分别处理对应的事件。
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case ACTION_USB_PERMISSION://接收到自定义广播
synchronized (this) {
UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { //允许权限申请
if (usbDevice != null) {
//读取所有设备
read(getUsbMass(usbDevice));
}
} else {
ToastUtil.showLongToast(SystemInfoActivity.this, "用户未授权,读取失败");
}
}
break;
case UsbManager.ACTION_USB_DEVICE_ATTACHED://接收到存储设备插入广播
UsbDevice device_add = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device_add != null) {
ToastUtil.showLongToast(SystemInfoActivity.this, "接收到存储设备插入广播");
listDevice();
}
break;
case UsbManager.ACTION_USB_DEVICE_DETACHED://接收到存储设备拔出广播
UsbDevice device_remove = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device_remove != null) {
ToastUtil.showLongToast(SystemInfoActivity.this, "接收到存储设备拔出广播");
usbFile = null;
}
break;
}
}
};
其中对应的事件为read()和listDevice()方法,代码如下。
//读取USB设备的根目录
public void read(UsbMassStorageDevice massDevice) {
try {
massDevice.init();//初始化
Partition partition = massDevice.getPartitions().get(0);
FileSystem currentFs = partition.getFileSystem();
UsbFile root = currentFs.getRootDirectory();//获取根目录
usbFile = root;
} catch (Exception e) {
e.printStackTrace();
ToastUtil.showLongToast(SystemInfoActivity.this, "读取失败");
}
}
public void listDevice() {
//获取管理者
UsbManager usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
//枚举设备
storageDevices = UsbMassStorageDevice.getMassStorageDevices(getContext());//获取存储设备
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
for (UsbMassStorageDevice device : storageDevices) {//可能有几个 一般只有一个 因为大部分手机只有1个otg插口
if (usbManager.hasPermission(device.getUsbDevice())) {//有就直接读取设备是否有权限
read(device);
} else {//没有就去发起意图申请
usbManager.requestPermission(device.getUsbDevice(), pendingIntent); //该代码执行后,系统弹出一个对话框
}
}
}
//获取USB设备
private UsbMassStorageDevice getUsbMass(UsbDevice usbDevice) {
for (UsbMassStorageDevice device : storageDevices) {
if (usbDevice.equals(device.getUsbDevice())) {
return device;
}
}
return null;
}
4.读写U盘dat文件
接下来就相对简单了,直接看代码。
//把本地文件写入到U盘中
public static void saveSDFile2OTG(File file, UsbFile usbFile) {
UsbFile usbSNFile = null;
//删除旧文件
UsbFile[] usbFiles;
try {
usbFiles = usbFile.listFiles();
if (usbFiles != null && usbFiles.length > 0) {
for (UsbFile file1 : usbFiles) {
if (file1.getName().equals("sn.dat")) {
file1.delete(); //删除U盘旧的sn.dat
break;
}
}
usbSNFile = usbFile.createFile(file.getName());
}
} catch (IOException e) {
e.printStackTrace();
}
try {
OutputStream os = new UsbFileOutputStream(usbSNFile);
FileInputStream is = new FileInputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[1024 * 8];
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer);
}
os.flush();
os.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
file.delete();//删除本机sn文件
}
//把U盘文件写入到本地文件中
public static void saveOTGFile2SD(File f, UsbFile usbFile) {
try {
FileOutputStream os = new FileOutputStream(f);
InputStream is = new UsbFileInputStream(usbFile);
int bytesRead = 0;
byte[] buffer = new byte[1024 * 8];
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
os.close();
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
然后看一下Main函数,我稍微简化了一下。
//本地存放路径
private static final String SDFILEPATH = "/storage/emulated/0/sn.dat";
//动态获取U盘权限
registerReceiver();
//这里必须先要list一下,为了弹出对话框手动允许获得U盘读写权限。
listDevice();
//U盘设置
mBtnUdiskSetNum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (usbFile != null) {
try {
int isHasSNfile = 0;//判断是否含有SN文件
for (UsbFile file : usbFile.listFiles()) {
if ("sn.dat".equals(file.getName())) {
File sdfile = new File(SDFILEPATH);//在SD卡根目录创建一个临时sn.dat文件
isHasSNfile = 1;
new Thread(new Runnable() {
@Override
public void run() {
saveOTGFile2SD(sdfile, file);//把U盘文件存入SD卡
try {
//不停一下会报错,下同
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
udiskReadSn.udiskInsertSN(SDFILEPATH, isSuccess, SNValue);//注入SN并修改sn.dat文件
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
saveSDFile2OTG(sdfile, usbFile);//把修改好的sn.dat存回U盘
}
}).start();
}
Log.i(TAG, file.getName());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
总结
以上就是全部关于U盘读取和写入dat数据文件的全部,新人创作,觉得不戳点个赞呗~
参考文章
Java实现.dat文件转txt可读文件
Android OTG 读写U盘文件
android OTG (USB读写,U盘读写)最全使用相关总结