最近在适配各种Android设备(非手机),都是深度定制过。需要往SD卡不停地写入实时音视频文件,但在对SD卡写文件时,都报Permission denied的错,前前后后调了很久才调通,现在写个文章来记录下,以备不时之需。
第一步是按内部存储和外部存储的区分来获取路径。下面这段代码是根据条件获取内、外存储路径:
/**
* 获取Android内部存储或外置SD卡路径
*
* @param context 上下文
* @param isRemovale true获取的是外部存储,false获取的是内部存储
* @return
*/
public static String getStoragePath(Context context, boolean isRemovale) {
// 拿到StorageManager
StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = null;
try {
// 反射获得StorageVolume
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
// 反射获得StorageManager的getVolumeList方法
Method getVolumeList = storageManager.getClass().getMethod("getVolumeList");
// 反射获得StorageVolume的getPath方法
Method getPath = storageVolumeClazz.getMethod("getPath");
// 拿到StorageVolume的isRemovable方法
Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
Object result = getVolumeList.invoke(storageManager); // 得到Storage Volume数组
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) { // 遍历数组,拿到StorageVolume中的路径mPath
Object storageVolume = Array.get(result, i);
String path = (String) getPath.invoke(storageVolume);
// 判断是否是可移除的,可移除表示是sdcard
boolean removable = (Boolean) isRemovable.invoke(storageVolume);
if (isRemovale == removable) { // isRemovale为true返回sdcard路径,false返回内部存储路径
return path;
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
这里我直接获取外部存储路径,即SD卡的路径:
/**
* 优先获取SD卡路径,若SD卡无,则返回内部存储路径
*
* @param context 上下文
* @return 返回可用路径
*/
public static String getActivePath(Context context) {
String sdCardPath = getStoragePath(context, true);//返回SD卡路径
String innerCPath = getStoragePath(context, false);//返回内部存储路径
Log.e("TAG", "sdCardPath=" + sdCardPath);
Log.e("TAG", "innerCPath=" + innerCPath);
if (sdCardPath != null && innerCPath != null) {
long sdCardSize = getTotalMemorySize(sdCardPath);
long innerCSize = getTotalMemorySize(innerCPath);
Log.e("TAG", "sdCardSize=" + sdCardSize + ",innerCSize=" + innerCSize);
if (sdCardSize > innerCSize) {
return sdCardPath;
} else {
return innerCPath;
}
}
return innerCPath;//否则返回
}
获取该存储大小:
/**
* 获取该存储空间大小
*
* @param path 存储路径
* @return 返回该存储路径的空间大小,大小为字节
*/
private static long getTotalMemorySize(String path) {
try {
//系统的空间描述类
StatFs stat = new StatFs(path);
//每个区块占字节数
long blockSize = stat.getBlockSize();
//区块总数
long totalBlocks = stat.getBlockCount();
return totalBlocks * blockSize;
}catch (Exception e){
e.printStackTrace();
}
return 0;
}
在需要调用的地方直接写 getActivePath(context)。一般来说,这样获取出来的路径都可用:
但是有时即使这样,当向SD卡读写文件时,依旧报错:Permission denied
这个权限的报错,网上很多人说需要添加相应的权限,但确定项目已经添加,但依旧报错。
后来才知道,该Android设备已被厂商深度定制过,很多权限都直接掐掉了。
解决方法有两个:
一是与厂商沟通,让其开放权限给你的App,但这样费时又费力;
二是直接在代码里写死路径:"/mnt/m_external_sd"(但发现此方法也不是万能,在有些设备上可行,某些设备上又不可以= =)
目前我的做法是直接写死路径,大家有更好的办法可以告诉下~