安卓6.0已经发布一段时间了,不过安卓6.0的新特性仍在发掘中。现在,又有一个有趣的新特性被发现了——安卓6.0可以把microSD卡也就是TF卡,作为设备的内置存储使用。这个新特性之所以被发现得晚,大概是由于能够率先升级安卓6.0的Nexus设备不支持TF卡扩展吧。
在之前,安卓系统虽然可以支持TF卡扩展,但TF卡扩展的容量只能当成外置存储。虽然用户可以把照片、音乐等内容放进去,但App不能安装到其中。在安卓6.0中,当首次插入TF卡的时候,系统会询问会将TF卡作为外置还是内置储存。如果选择作为内置存储,系统则会对TF卡格式化并加密——注意,这会丢失数据!然后,TF卡的容量就和设备的内置存储融合了。
一般来说,设备的内置存储速度会比TF卡快上不少,稳定性也更高,因此如果不是容量特别紧张,并不建议把TF当成内置储存植入。同时,市面上支持TF卡扩展的安卓设备也越来越少,安卓6.0的这个新特性也许会遭到冷遇。
Android手机上的外置SD卡,起初的时候,即在Android出世的前几年,那时手机的存储是十分有限的,不像现在到处可见16G、32G和64G的存储,因而那时候的手机有的厂商允许插入外置的SD卡,此时这张卡仍处于手机的扩展部分。后来,随着手机的发展以及存储能力的增加,这张外置SD卡,逐渐成为了手机的一部分,不再允许可插拔了,当然现在依然有的手机允许对存储进行拓展,比如三星等。
那张拓展的存储卡,现在叫做TF卡,且不是所有的手机都支持它,但是有时候有些奇葩需求偏要优先存储在TF卡里面,这叫不得不要求开发人员去检查这张卡是否存在、是否可用。又因为这是手机厂商可拓展、可自定义的部分,所有不同厂商生产的手机,以及同一厂商生产的不同型号的手机,TF卡的位置都相差很大,并没有一个统一的名称或位置。因而这是比较困难的一部分,但是还好Android是开源的,我们可以通过运行时来判断手机是否有TF卡,以及TF卡是否可用。
下面这个方法可以获取手机的可以存储,包括SD卡、TF卡等,对多存储卡进行了匹配,详细的代码如下
写在最前面:
由于公司的项目里有个视频下载的功能,而且这个是产品比较重要的功能。但是,由于众所周知的原因,通过传统方式获取的SD卡路径,在不同厂商的设备上都不准确,可能SD卡和内存存储介质倒置了,也可能获取出来的路径无法读写。就算是相同厂商不同的产品,获取出来的SD卡路径和内置存储路径都是五花八门。
网上到处找资料,还是没法完全解决上述问题,连有些主流的机型都无法覆盖。经过一段时间探索,算是解决了问题。
Environment里有这样一个方法isExternalStorageRemovable(),注释如下,大概意思是:
如果返回true,external storage是用户可以移除的,如SD卡、U盘(这一项是我自己加的)等。如果返回false,说明external是集成到设备中的,不可以进行物理移除。
核心:SD卡对系统而言是可移除的,而内置存储不可以移除。
解决思路有多种:
第一种:
用反射,调用
StorageManager类的隐藏方法
- getVolumeList()
和
StorageVolume类的隐藏方法
- getPath()
- isRemovable()
- getState()
这里需要注意的是getState方法不一定在所有版本中都有,对比多个版本的源码后得知,此方法是在4.4_r1之后新增的,使用时需要注意,要判断磁盘的挂载状态,不能只依赖getState。另外,不要试图调用StorageVolume类中的其它方法,原因上面提过,本人也对比过,有些方法在其它版本中不一定有,比如isPrimary()——是否是主存储器,就是在4.2_r1版本之后才有的方法。
第二种:
看系统设置APP中Storage模块的具体实现。既然系统设置中可以正确的获取到SD卡位置,那么可以看看SettingActivity到底是怎么做的(我还没有具体去看,但可以确定的是,SettingActivity里也利用了StorageManager的隐藏方法,只不过SettingActivity里用的是getDisks()来获取磁盘信息,如果要用这些方法,还是得用反射)。
- Setting模块的源码:
https://github.com/android/platform_packages_apps_settings/tree/master/src/com/android/settings
- Storage模块的位置:
- Deviceinfo/StorageSettings
- clone到AndroidStudio里更方便查看。
第三种:
这个是看得别人的,在Environment类里找到的方法。但是,也是由于版本问题,在部分低版本和高版本上无法使用,所以不建议使用。两行代码:
- SD卡:System.getenv(“SECONDARY_STORAGE“)
- 内置存储:System.getenv(“EXTERNAL_STORAGE“)
他们返回的都是path
这里使用第一种方法,具体步骤(完整代码后在会面贴出):
①.获取StorageManager
1
|
final
StorageManager
storageManager
=
(
StorageManager
)
pContext
.
getSystemService
(
Context
.
STORAGE_SERVICE
)
;
|
②.反射得到StorageManger里的getVolumeList()方法
③.反射得到StorageVolume类的对象
1
2
|
//得到StorageVolume类的对象
finalClass
<
?
>
storageValumeClazz
=
Class
.
forName
(
"android.os.storage.StorageVolume"
)
;
|
④.反射得到StorageVolume类里的getPath()、isRemovable()、getState()方法
⑤.反射获取属性的核心方法,最终会得到每个StorageVolume对象的path、removable和state属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//调用getVolumeList方法,参数为:“谁”中调用这个方法
final
Objectinvoke
VolumeList
=
getVolumeList
.
invoke
(
storageManager
)
;
final
int
length
=
Array
.
getLength
(
invokeVolumeList
)
;
ArrayList
list
=
new
ArrayList
<>
(
)
;
for
(
int
i
=
0
;
i
<
length
;
i
++
)
{
final
Object
storageValume
=
Array
.
get
(
invokeVolumeList
,
i
)
;
//得到StorageVolume对象
final
Stringpath
=
(
String
)
getPath
.
invoke
(
storageValume
)
;
f
inal
booleanremovable
=
(
Boolean
)
isRemovable
.
invoke
(
storageValume
)
;
Stringstate
=
null
;
if
(
mGetState
!=
null
)
{
state
=
(
String
)
mGetState
.
invoke
(
storageValume
)
;
}
else
{
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
KITKAT
)
{
state
=
Environment
.
getStorageState
(
newFile
(
path
)
)
;
}
else
{
if
(
removable
)
{
state
=
EnvironmentCompat
.
getStorageState
(
newFile
(
path
)
)
;
}
else
{
//不能移除的存储介质,一直是mounted
state
=
Environment
.
MEDIA_MOUNTED
;
}
final
FileexternalStorageDirectory
=
Environment
.
getExternalStorageDirectory
(
)
;
Log
.
e
(
TAG
,
"externalStorageDirectory=="
+
externalStorageDirectory
)
;
}
}
}
|
经过这几步,SD卡路径已经能完美获取了,而且准确无误(就目前测试过的设备而言)。具体代码可以下载我写的demo。
源码戳这里:https://github.com/gongshoudao/SDcardScanner
如果今后有时间,再研究一下上面提到的第二种方式。
博客参考 http://blog.csdn.net/zqs62761130/article/details/42464785