外部SDカードの場所を見つけるための普遍的な方法はありますか?
外部記憶装置 と混同しないでください。 Environment.getExternalStorageState()
は、 "/ mnt/sdcard"のように内部SDマウントポイントへのパスを返します。しかし、問題は外部SDについてです。 "/ mnt/sdcard/external_sd"のようなパスを取得するにはどうすればよいですか(デバイスごとに異なる場合があります)。
私はファイルシステム名によるmount
コマンドのフィルタリングで終わることになると思います。しかし、私はこの方法が十分に堅牢であるかどうかわからない。
Environment.getExternalStorageState()
は "/ mnt/sdcard"のように内部SDマウントポイントへのパスを返します
いいえ、Environment.getExternalStorageDirectory()
は、デバイス製造元が「外部記憶装置」と見なしたものすべてを指します。一部のデバイスでは、これはSDカードなどのリムーバブルメディアです。一部のデバイスでは、これはデバイス上のフラッシュの一部です。ここで、「外部ストレージ」とは、少なくともAndroid 1.xおよび2.xでは、「ホストマシンにマウントされたときにUSBマスストレージモードを介してアクセス可能なもの」を意味します。
しかし、問題は外部SDについてです。 "/ mnt/sdcard/external_sd"のようなパスを取得するにはどうすればよいですか(デバイスごとに異なる場合があります)。
Androidには、前述のように外部ストレージ以外に「外部SD」という概念はありません。
デバイスメーカーが外部記憶装置をオンボードフラッシュにしてSDカードを持っていることを選択した場合は、そのメーカーに連絡してSDカードを使用できるかどうか(保証されていません)使用するパスなど、それを使用する。
UPDATE
最近注目したことが2つあります。
まず、Android 4.4以降では、getExternalFilesDirs()
およびgetExternalCacheDirs()
によって返される可能性があるそのメディア上の場所を除いて、リムーバブルメディア(たとえば「外部SD」)への書き込みアクセス権がありません。特に低レベルの詳細が必要な場合は、 Dave Smithの優れた分析 を参照してください。
次に、リムーバブルメディアへのアクセスがそれ以外の場合はAndroid SDKの一部であるかどうかについて誰かに悩まされないように、ここに Dianne Hackbornの評価 :
...覚えておいてください:アンドロイド4.4まで、公式のAndroidプラットフォームは2つの特別な場合を除いてすべてでSDカードをサポートしていません:外部記憶装置がある古い学校のストレージレイアウトSDカード(現在もプラットフォームでサポートされています)、およびAndroid 3.0に追加された小さなSDカードをスキャンしてメディアプロバイダに追加し、アプリに読み取り専用アクセスを許可する小さな機能(これもまだ今日のプラットフォームでサポートされています。
Android 4.4は、アプリケーションがストレージにSDカードを使用することを実際に許可したプラットフォームの最初のリリースです。それ以前のそれらへのアクセスは、プライベートな、サポートされていないAPIを介したものでした。私たちは現在、プラットフォームに非常に豊富なAPIを持っています。これにより、アプリケーションは以前よりも優れた方法でサポートされた方法でSDカードを利用できます。アプリ内のアクセス許可、およびSDカード上の他のファイルへのアクセスは、特別な許可を必要とせずにファイルピッカーを通過する限り可能です。
私はここで見つけたいくつかの答えに基づいて以下の解決策を思い付きました。
コード:
public class ExternalStorage {
public static final String SD_CARD = "sdCard";
public static final String EXTERNAL_SD_CARD = "externalSdCard";
/**
* @return True if the external storage is available. False otherwise.
*/
public static boolean isAvailable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
public static String getSdCardPath() {
return Environment.getExternalStorageDirectory().getPath() + "/";
}
/**
* @return True if the external storage is writable. False otherwise.
*/
public static boolean isWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/**
* @return A map of all storage locations available
*/
public static Map<String, File> getAllStorageLocations() {
Map<String, File> map = new HashMap<String, File>(10);
List<String> mMounts = new ArrayList<String>(10);
List<String> mVold = new ArrayList<String>(10);
mMounts.add("/mnt/sdcard");
mVold.add("/mnt/sdcard");
try {
File mountFile = new File("/proc/mounts");
if(mountFile.exists()){
Scanner scanner = new Scanner(mountFile);
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (line.startsWith("/dev/block/vold/")) {
String[] lineElements = line.split(" ");
String element = lineElements[1];
// don't add the default mount path
// it's already in the list.
if (!element.equals("/mnt/sdcard"))
mMounts.add(element);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File voldFile = new File("/system/etc/vold.fstab");
if(voldFile.exists()){
Scanner scanner = new Scanner(voldFile);
while (scanner.hasNext()) {
String line = scanner.nextLine();
if (line.startsWith("dev_mount")) {
String[] lineElements = line.split(" ");
String element = lineElements[2];
if (element.contains(":"))
element = element.substring(0, element.indexOf(":"));
if (!element.equals("/mnt/sdcard"))
mVold.add(element);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < mMounts.size(); i++) {
String mount = mMounts.get(i);
if (!mVold.contains(mount))
mMounts.remove(i--);
}
mVold.clear();
List<String> mountHash = new ArrayList<String>(10);
for(String mount : mMounts){
File root = new File(mount);
if (root.exists() && root.isDirectory() && root.canWrite()) {
File[] list = root.listFiles();
String hash = "[";
if(list!=null){
for(File f : list){
hash += f.getName().hashCode()+":"+f.length()+", ";
}
}
hash += "]";
if(!mountHash.contains(hash)){
String key = SD_CARD + "_" + map.size();
if (map.size() == 0) {
key = SD_CARD;
} else if (map.size() == 1) {
key = EXTERNAL_SD_CARD;
}
mountHash.add(hash);
map.put(key, root);
}
}
}
mMounts.clear();
if(map.isEmpty()){
map.put(SD_CARD, Environment.getExternalStorageDirectory());
}
return map;
}
}
SAGE:
Map<String, File> externalLocations = ExternalStorage.getAllStorageLocations();
File sdCard = externalLocations.get(ExternalStorage.SD_CARD);
File externalSdCard = externalLocations.get(ExternalStorage.EXTERNAL_SD_CARD);
ユーザーが何かを保存したい場所の場所を選択する必要があるListPreference
を使用するアプリケーションがありました。そのアプリで、私はSDカードマウントポイントのために/ proc/mountsと/system/etc/vold.fstabをスキャンしました。各ファイルのマウントポイントを2つの別々のArrayList
に格納しました。
次に、1つのリストを他のリストと比較し、両方のリストにない項目を破棄しました。それは私に各SDカードへのルートパスのリストを与えました。
そこから、File.exists()
、File.isDirectory()
、およびFile.canWrite()
を使用してパスをテストしました。これらのテストのいずれかが誤っていたら、リストからそのパスを破棄しました。
リストに何が残っていても、ListPreference
values属性で使用できるようにString[]
配列に変換しました。
あなたはここでコードを見ることができます: http://sapienmobile.com/?p=204
あなたは ContextCompat.getExternalFilesDirs() というサポートライブラリ関数を使用することを試みることができます。
final File[] appsDir=ContextCompat.getExternalFilesDirs(getActivity(),null);
final ArrayList<File> extRootPaths=new ArrayList<>();
for(final File file : appsDir)
extRootPaths.add(file.getParentFile().getParentFile().getParentFile().getParentFile());
最初のものは一次外部記憶装置です、そして残りは本当のSDカードパスであることになっています。
".getParentFile()"が複数あるのは、元のパスが
.../Android/data/YOUR_APP_PACKAGE_NAME/files/
編集:これは私が作成した、SDカードのパスを取得するためのより包括的な方法です。
/**
* returns a list of all available sd cards paths, or null if not found.
*
* @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static List<String> getSdCardPaths(final Context context, final boolean includePrimaryExternalStorage)
{
final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
if(externalCacheDirs==null||externalCacheDirs.length==0)
return null;
if(externalCacheDirs.length==1)
{
if(externalCacheDirs[0]==null)
return null;
final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
if(!Environment.MEDIA_MOUNTED.equals(storageState))
return null;
if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
return null;
}
final List<String> result=new ArrayList<>();
if(includePrimaryExternalStorage||externalCacheDirs.length==1)
result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
for(int i=1;i<externalCacheDirs.length;++i)
{
final File file=externalCacheDirs[i];
if(file==null)
continue;
final String storageState=EnvironmentCompat.getStorageState(file);
if(Environment.MEDIA_MOUNTED.equals(storageState))
result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
}
if(result.isEmpty())
return null;
return result;
}
/** Given any file/folder inside an sd card, this will return the path of the sd card */
private static String getRootOfInnerSdCardFolder(File file)
{
if(file==null)
return null;
final long totalSpace=file.getTotalSpace();
while(true)
{
final File parentFile=file.getParentFile();
if(parentFile==null||parentFile.getTotalSpace()!=totalSpace)
return file.getAbsolutePath();
file=parentFile;
}
}
すべての 外部記憶装置 (それらが SDカード か 内部取り外し不可能な記憶装置 かどうかを取得するため_)、あなたは次のコードを使用することができます。
final String state = Environment.getExternalStorageState();
if ( Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) ) { // we can read the External Storage...
//Retrieve the primary External Storage:
final File primaryExternalStorage = Environment.getExternalStorageDirectory();
//Retrieve the External Storages root directory:
final String externalStorageRootDir;
if ( (externalStorageRootDir = primaryExternalStorage.getParent()) == null ) { // no parent...
Log.d(TAG, "External Storage: " + primaryExternalStorage + "\n");
}
else {
final File externalStorageRoot = new File( externalStorageRootDir );
final File[] files = externalStorageRoot.listFiles();
for ( final File file : files ) {
if ( file.isDirectory() && file.canRead() && (file.listFiles().length > 0) ) { // it is a real directory (not a USB drive)...
Log.d(TAG, "External Storage: " + file.getAbsolutePath() + "\n");
}
}
}
}
あるいは、System.getenv( "EXTERNAL_STORAGE")を使用してプライマリ外部ストレージディレクトリ(例: "/ storage/sdcard0" )とSystem.getenv( " SECONDARY_STORAGE ")すべての二次ディレクトリのリストを回収する(例: "/storage/extSdCard:/ storage/UsbDriveA:/ storage/UsbDriveB " )。この場合も、USBドライブを除外するために2次ディレクトリのリストをフィルタ処理することをお勧めします。
いずれにせよ、ハードコードされたパスを使用することは常に悪いアプローチであることに注意してください(特にすべての製造業者が喜んでそれを変更する可能性がある場合)。
Richardのように私も/ proc/mountsファイルを使って利用可能なストレージオプションのリストを取得します
public class StorageUtils {
private static final String TAG = "StorageUtils";
public static class StorageInfo {
public final String path;
public final boolean internal;
public final boolean readonly;
public final int display_number;
StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
this.path = path;
this.internal = internal;
this.readonly = readonly;
this.display_number = display_number;
}
public String getDisplayName() {
StringBuilder res = new StringBuilder();
if (internal) {
res.append("Internal SD card");
} else if (display_number > 1) {
res.append("SD card " + display_number);
} else {
res.append("SD card");
}
if (readonly) {
res.append(" (Read only)");
}
return res.toString();
}
}
public static List<StorageInfo> getStorageList() {
List<StorageInfo> list = new ArrayList<StorageInfo>();
String def_path = Environment.getExternalStorageDirectory().getPath();
boolean def_path_internal = !Environment.isExternalStorageRemovable();
String def_path_state = Environment.getExternalStorageState();
boolean def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED)
|| def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
boolean def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
BufferedReader buf_reader = null;
try {
HashSet<String> paths = new HashSet<String>();
buf_reader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
int cur_display_number = 1;
Log.d(TAG, "/proc/mounts");
while ((line = buf_reader.readLine()) != null) {
Log.d(TAG, line);
if (line.contains("vfat") || line.contains("/mnt")) {
StringTokenizer tokens = new StringTokenizer(line, " ");
String unused = tokens.nextToken(); //device
String mount_point = tokens.nextToken(); //mount point
if (paths.contains(mount_point)) {
continue;
}
unused = tokens.nextToken(); //file system
List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags
boolean readonly = flags.contains("ro");
if (mount_point.equals(def_path)) {
paths.add(def_path);
list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1));
} else if (line.contains("/dev/block/vold")) {
if (!line.contains("/mnt/secure")
&& !line.contains("/mnt/asec")
&& !line.contains("/mnt/obb")
&& !line.contains("/dev/mapper")
&& !line.contains("tmpfs")) {
paths.add(mount_point);
list.add(new StorageInfo(mount_point, false, readonly, cur_display_number++));
}
}
}
}
if (!paths.contains(def_path) && def_path_available) {
list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1));
}
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (buf_reader != null) {
try {
buf_reader.close();
} catch (IOException ex) {}
}
}
return list;
}
}
今回はこのトピック内のすべての解決策を試してください。しかし、それらのすべてが1つの外付け(取り外し可能)と1つの内蔵(取り外し不可能)カードを持つデバイス上で正しく動作しませんでした。外付けカードのパスは 'mount'コマンド、 'proc/mounts'ファイルなどから取得できません。
そして(Paulo Luanの)独自のソリューションを作ります。
String sSDpath = null;
File fileCur = null;
for( String sPathCur : Arrays.asList( "ext_card", "external_sd", "ext_sd", "external", "extSdCard", "externalSdCard")) // external sdcard
{
fileCur = new File( "/mnt/", sPathCur);
if( fileCur.isDirectory() && fileCur.canWrite())
{
sSDpath = fileCur.getAbsolutePath();
break;
}
}
fileCur = null;
if( sSDpath == null) sSDpath = Environment.getExternalStorageDirectory().getAbsolutePath();
Android.os.Environment
のソースコードを見ると、Androidはパスの環境変数に大きく依存していることがわかります。 "SECONDARY_STORAGE"環境変数を使用して、リムーバブルSDカードへのパスを見つけることができます。
/**
* Get a file using an environmental variable.
*
* @param variableName
* The Environment variable name.
* @param paths
* Any paths to the file if the Environment variable was not found.
* @return the File or {@code null} if the File could not be located.
*/
private static File getDirectory(String variableName, String... paths) {
String path = System.getenv(variableName);
if (!TextUtils.isEmpty(path)) {
if (path.contains(":")) {
for (String _path : path.split(":")) {
File file = new File(_path);
if (file.exists()) {
return file;
}
}
} else {
File file = new File(path);
if (file.exists()) {
return file;
}
}
}
if (paths != null && paths.length > 0) {
for (String _path : paths) {
File file = new File(_path);
if (file.exists()) {
return file;
}
}
}
return null;
}
使用例
public static final File REMOVABLE_STORAGE = getDirectory("SECONDARY_STORAGE");
単にこれを使う:
String primary_sd = System.getenv("EXTERNAL_STORAGE");
if(primary_sd != null)
Log.i("EXTERNAL_STORAGE", primary_sd);
String secondary_sd = System.getenv("SECONDARY_STORAGE");
if(secondary_sd != null)
Log.i("SECONDARY_STORAGE", secondary_sd)
外部SDカードの場所を見つけるための普遍的な方法はありますか?
あなたが正式な方法を意味する場合、普遍的な方法で。はい、あります。
APIレベル19、つまりAndroidバージョン4.4 KitKatでは、アプリがデータ/ファイルをマイクロSDカードに保存できるようにするContext
クラスにFile[] getExternalFilesDirs (String type)
を追加しました。
Android 4.4は、アプリがストレージにSDカードを使用することを実際に許可したプラットフォームの最初のリリースです。 APIレベル19より前のSDカードへのアクセスは、非サポートのプライベートAPIを介して行われていました。
getExternalFilesDirs(String type) すべての共有/外部ストレージデバイス上のアプリケーション固有のディレクトリへの絶対パスを返します。つまり、内部メモリと外部メモリの両方へのパスが返されます。一般的に、2番目に返されたパスは(存在する場合)microSDカードの保存パスになります。
しかし、それに注意してください、
ユーザーがリムーバブルメディアを取り出すことができるため、共有ストレージが常に利用できるとは限りません。メディアの状態は
getExternalStorageState(File)
を使って確認できます。これらのファイルに対して強化されたセキュリティはありません。たとえば、
WRITE_EXTERNAL_STORAGE
を保持しているアプリケーションはこれらのファイルに書き込むことができます。
Google /公式Androidドキュメントによると、内部ストレージと外部ストレージの用語はでまったく異なります私たちの考えから.
これは私が外付けカードを見つけるために使う方法です。 mount cmd returnを使用してvfat部分を解析します。
String s = "";
try {
Process process = new ProcessBuilder().command("mount")
.redirectErrorStream(true).start();
process.waitFor();
InputStream is = process.getInputStream();
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
s = s + new String(buffer);
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}
//用行分隔mount列表
String[] lines = s.split("\n");
for(int i=0; i<lines.length; i++) {
//如果行内有挂载路径且为vfat类型,说明可能是内置或者外置sd的挂载点
if(-1 != lines[i].indexOf(path[0]) && -1 != lines[i].indexOf("vfat")) {
//再用空格分隔
String[] blocks = lines[i].split("\\s");
for(int j=0; j<blocks.length; j++) {
//判断是否是挂载为vfat类型
if(-1 != blocks[j].indexOf(path[0])) {
//Test if it is the external sd card.
}
}
}
}
以下のコードを書くことによってあなたは場所を得るでしょう:
/ storage/663D-554E/Android/data/app_package_name/files /
これはあなたのアプリのデータをsd_card内の/ Android/dataに保存します。
File[] list = ContextCompat.getExternalFilesDirs(MainActivity.this, null);
list[1]+"/fol"
場所を取得するために、ファイル配列への内部に0、SDカードに1を渡します。
私はこのコードをmoto g4 plusとSamsungデバイスでテストしました(すべてうまくいきます)。
これが役に立つかもしれないことを願っています。
この解決法はSystem.getenv("SECONDARY_STORAGE")
がMarshmallowには役に立たないという事実を扱います。
テスト済みで作業中
サムスンギャラクシータブA(アンドロイド6.0.1 - 株式)
/**
* Returns all available external SD-Card roots in the system.
*
* @return paths to all available external SD-Card roots in the system.
*/
public static String[] getStorageDirectories() {
String [] storageDirectories;
String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat) {
List<String> results = new ArrayList<String>();
File[] externalDirs = applicationContext.getExternalFilesDirs(null);
for (File file : externalDirs) {
String path = file.getPath().split("/Android")[0];
if((Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop && Environment.isExternalStorageRemovable(file))
|| rawSecondaryStoragesStr != null && rawSecondaryStoragesStr.contains(path)){
results.add(path);
}
}
storageDirectories = results.toArray(new String[0]);
}else{
final Set<String> rv = new HashSet<String>();
if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
Collections.addAll(rv, rawSecondaryStorages);
}
storageDirectories = rv.toArray(new String[rv.size()]);
}
return storageDirectories;
}
上記の私の最初の答え以来、voldをスキャンすることはもはやさまざまな製造業者にわたって実行可能ではありません。
私はより信頼性が高く簡単な方法を開発しました。
File mnt = new File("/storage");
if (!mnt.exists())
mnt = new File("/mnt");
File[] roots = mnt.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() && pathname.exists()
&& pathname.canWrite() && !pathname.isHidden()
&& !isSymlink(pathname);
}
});
ルートには、USB接続されたUSBデバイスを含む、システム上のすべての書き込み可能なルートディレクトリが含まれます。
注:canWriteメソッドにはAndroid.permission.WRITE_EXTERNAL_STORAGEパーミッションが必要です。
それはとても遅くなりました、しかし最終的に私は(製造元とAndroidのバージョンによって)ほとんどのデバイスをテストしたものを持っていますそのアンドロイド2.2+での作業。機能していないとわかった場合は、デバイス名を付けてコメントしてください。私はそれを修正します。興味のある人は私はそれがどのように動作するかを説明します。
import Java.io.BufferedReader;
import Java.io.File;
import Java.io.FileInputStream;
import Java.io.FilenameFilter;
import Java.io.IOException;
import Java.io.InputStreamReader;
import Android.util.Log;
/**
* @author ajeet
*05-Dec-2014 2014
*
*/
public class StorageUtil {
public boolean isRemovebleSDCardMounted() {
File file = new File("/sys/class/block/");
File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
boolean flag = false;
for (File mmcfile : files) {
File scrfile = new File(mmcfile, "device/scr");
if (scrfile.exists()) {
flag = true;
break;
}
}
return flag;
}
public String getRemovebleSDCardPath() throws IOException {
String sdpath = null;
File file = new File("/sys/class/block/");
File[] files = file.listFiles(new MmcblkFilter("mmcblk\\d$"));
String sdcardDevfile = null;
for (File mmcfile : files) {
Log.d("SDCARD", mmcfile.getAbsolutePath());
File scrfile = new File(mmcfile, "device/scr");
if (scrfile.exists()) {
sdcardDevfile = mmcfile.getName();
Log.d("SDCARD", mmcfile.getName());
break;
}
}
if (sdcardDevfile == null) {
return null;
}
FileInputStream is;
BufferedReader reader;
files = file.listFiles(new MmcblkFilter(sdcardDevfile + "p\\d+"));
String deviceName = null;
if (files.length > 0) {
Log.d("SDCARD", files[0].getAbsolutePath());
File devfile = new File(files[0], "dev");
if (devfile.exists()) {
FileInputStream fis = new FileInputStream(devfile);
reader = new BufferedReader(new InputStreamReader(fis));
String line = reader.readLine();
deviceName = line;
}
Log.d("SDCARD", "" + deviceName);
if (deviceName == null) {
return null;
}
Log.d("SDCARD", deviceName);
final File mountFile = new File("/proc/self/mountinfo");
if (mountFile.exists()) {
is = new FileInputStream(mountFile);
reader = new BufferedReader(new InputStreamReader(is));
String line = null;
while ((line = reader.readLine()) != null) {
// Log.d("SDCARD", line);
// line = reader.readLine();
// Log.d("SDCARD", line);
String[] mPonts = line.split("\\s+");
if (mPonts.length > 6) {
if (mPonts[2].trim().equalsIgnoreCase(deviceName)) {
if (mPonts[4].contains(".Android_secure")
|| mPonts[4].contains("asec")) {
continue;
}
sdpath = mPonts[4];
Log.d("SDCARD", mPonts[4]);
}
}
}
}
}
return sdpath;
}
static class MmcblkFilter implements FilenameFilter {
private String pattern;
public MmcblkFilter(String pattern) {
this.pattern = pattern;
}
@Override
public boolean accept(File dir, String filename) {
if (filename.matches(pattern)) {
return true;
}
return false;
}
}
}
私はSDカードがデバイス上で利用可能かどうかをチェックするutilsメソッドを作成し、利用可能な場合はデバイス上のSDカードパスを取得します。
あなたが必要とするあなたのプロジェクトのクラスに下記の2つのメソッドをコピーすることができます。それで全部です。
public String isRemovableSDCardAvailable() {
final String FLAG = "mnt";
final String SECONDARY_STORAGE = System.getenv("SECONDARY_STORAGE");
final String EXTERNAL_STORAGE_DOCOMO = System.getenv("EXTERNAL_STORAGE_DOCOMO");
final String EXTERNAL_SDCARD_STORAGE = System.getenv("EXTERNAL_SDCARD_STORAGE");
final String EXTERNAL_SD_STORAGE = System.getenv("EXTERNAL_SD_STORAGE");
final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE");
Map<Integer, String> listEnvironmentVariableStoreSDCardRootDirectory = new HashMap<Integer, String>();
listEnvironmentVariableStoreSDCardRootDirectory.put(0, SECONDARY_STORAGE);
listEnvironmentVariableStoreSDCardRootDirectory.put(1, EXTERNAL_STORAGE_DOCOMO);
listEnvironmentVariableStoreSDCardRootDirectory.put(2, EXTERNAL_SDCARD_STORAGE);
listEnvironmentVariableStoreSDCardRootDirectory.put(3, EXTERNAL_SD_STORAGE);
listEnvironmentVariableStoreSDCardRootDirectory.put(4, EXTERNAL_STORAGE);
File externalStorageList[] = null;
if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.KitKat) {
externalStorageList = getContext().getExternalFilesDirs(null);
}
String directory = null;
int size = listEnvironmentVariableStoreSDCardRootDirectory.size();
for (int i = 0; i < size; i++) {
if (externalStorageList != null && externalStorageList.length > 1 && externalStorageList[1] != null)
directory = externalStorageList[1].getAbsolutePath();
else
directory = listEnvironmentVariableStoreSDCardRootDirectory.get(i);
directory = canCreateFile(directory);
if (directory != null && directory.length() != 0) {
if (i == size - 1) {
if (directory.contains(FLAG)) {
Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
return directory;
} else {
return null;
}
}
Log.e(getClass().getSimpleName(), "SD Card's directory: " + directory);
return directory;
}
}
return null;
}
/**
* Check if can create file on given directory. Use this enclose with method
* {@link BeginScreenFragement#isRemovableSDCardAvailable()} to check sd
* card is available on device or not.
*
* @param directory
* @return
*/
public String canCreateFile(String directory) {
final String FILE_DIR = directory + File.separator + "hoang.txt";
File tempFlie = null;
try {
tempFlie = new File(FILE_DIR);
FileOutputStream fos = new FileOutputStream(tempFlie);
fos.write(new byte[1024]);
fos.flush();
fos.close();
Log.e(getClass().getSimpleName(), "Can write file on this directory: " + FILE_DIR);
} catch (Exception e) {
Log.e(getClass().getSimpleName(), "Write file error: " + e.getMessage());
return null;
} finally {
if (tempFlie != null && tempFlie.exists() && tempFlie.isFile()) {
// tempFlie.delete();
tempFlie = null;
}
}
return directory;
}
理由はわかりませんが、パブリックストレージディレクトリに作成されたFileに対してそれを使用する前に.createNewFile()を呼び出す必要があります。フレームワークの中で、そのメソッドに対するコメントはそれが有用ではないと言います。これがサンプルです...
String myPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS) + File.separator + "My Directory";
final File myDir = new File(myPath);
try {
myDir.mkdirs();
} catch (Exception ex) {
Toast.makeText(this, "error: " + ex.getMessage(), Toast.LENGTH_LONG).show();
}
String fname = "whatever";
File newFile = new File(myDir, fname);
Log.i(TAG, "File exists --> " + newFile.exists()) //will be false
try {
if (newFile.createNewFile()) {
//continue
} else {
Log.e(TAG, "error creating file");
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
</code>
これは私が取り外し可能なSDカードを見つけるために使う方法です。これは複雑で、場合によってはやり過ぎるかもしれませんが、ここ数年でテストしたさまざまなAndroidバージョンおよびデバイスメーカーで機能します。マウントされているものがあれば、それがSDカードを見つけられないAPIレベル15以降、私はデバイスを知りません。ほとんどの場合、特に既知のファイルの名前を指定した場合は、誤検知が返されることはありません。
うまくいかない場合に遭遇した場合はお知らせください
import Android.content.Context;
import Android.os.Build;
import Android.os.Environment;
import Android.support.v4.content.ContextCompat;
import Android.text.TextUtils;
import Android.util.Log;
import Java.io.BufferedReader;
import Java.io.File;
import Java.io.FileReader;
import Java.io.IOException;
import Java.util.ArrayList;
import Java.util.Collection;
import Java.util.Iterator;
import Java.util.LinkedHashSet;
import Java.util.Locale;
import Java.util.regex.Pattern;
public class SDCard {
private static final String TAG = "SDCard";
/** In some scenarios we can expect to find a specified file or folder on SD cards designed
* to work with this app. If so, set KNOWNFILE to that filename. It will make our job easier.
* Set it to null otherwise. */
private static final String KNOWNFILE = null;
/** Common paths for microSD card. **/
private static String[] commonPaths = {
// Some of these taken from
// https://stackoverflow.com/questions/13976982/removable-storage-external-sdcard-path-by-manufacturers
// These are roughly in order such that the earlier ones, if they exist, are more sure
// to be removable storage than the later ones.
"/mnt/Removable/MicroSD",
"/storage/removable/sdcard1", // !< Sony Xperia Z1
"/Removable/MicroSD", // Asus ZenPad C
"/removable/microsd",
"/external_sd", // Samsung
"/_ExternalSD", // some LGs
"/storage/extSdCard", // later Samsung
"/storage/extsdcard", // Main filesystem is case-sensitive; FAT isn't.
"/mnt/extsd", // some Chinese tablets, e.g. Zeki
"/storage/sdcard1", // If this exists it's more likely than sdcard0 to be removable.
"/mnt/extSdCard",
"/mnt/sdcard/external_sd",
"/mnt/external_sd",
"/storage/external_SD",
"/storage/ext_sd", // HTC One Max
"/mnt/sdcard/_ExternalSD",
"/mnt/sdcard-ext",
"/sdcard2", // HTC One M8s
"/sdcard1", // Sony Xperia Z
"/mnt/media_rw/sdcard1", // 4.4.2 on CyanogenMod S3
"/mnt/sdcard", // This can be built-in storage (non-removable).
"/sdcard",
"/storage/sdcard0",
"/emmc",
"/mnt/emmc",
"/sdcard/sd",
"/mnt/sdcard/bpemmctest",
"/mnt/external1",
"/data/sdext4",
"/data/sdext3",
"/data/sdext2",
"/data/sdext",
"/storage/microsd" //ASUS ZenFone 2
// If we ever decide to support USB OTG storage, the following paths could be helpful:
// An LG Nexus 5 apparently uses usb://1002/UsbStorage/ as a URI to access an SD
// card over OTG cable. Other models, like Galaxy S5, use /storage/UsbDriveA
// "/mnt/usb_storage",
// "/mnt/UsbDriveA",
// "/mnt/UsbDriveB",
};
/** Find path to removable SD card. */
public static File findSdCardPath(Context context) {
String[] mountFields;
BufferedReader bufferedReader = null;
String lineRead = null;
/** Possible SD card paths */
LinkedHashSet<File> candidatePaths = new LinkedHashSet<>();
/** Build a list of candidate paths, roughly in order of preference. That way if
* we can't definitively detect removable storage, we at least can pick a more likely
* candidate. */
// Could do: use getExternalStorageState(File path), with and without an argument, when
// available. With an argument is available since API level 21.
// This may not be necessary, since we also check whether a directory exists and has contents,
// which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.
// I moved hard-coded paths toward the end, but we need to make sure we put the ones in
// backwards order that are returned by the OS. And make sure the iterators respect
// the order!
// This is because when multiple "external" storage paths are returned, it's always (in
// experience, but not guaranteed by documentation) with internal/emulated storage
// first, removable storage second.
// Add value of environment variables as candidates, if set:
// EXTERNAL_STORAGE, SECONDARY_STORAGE, EXTERNAL_SDCARD_STORAGE
// But note they are *not* necessarily *removable* storage! Especially EXTERNAL_STORAGE.
// And they are not documented (API) features. Typically useful only for old versions of Android.
String val = System.getenv("SECONDARY_STORAGE");
if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
val = System.getenv("EXTERNAL_SDCARD_STORAGE");
if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
// Get listing of mounted devices with their properties.
ArrayList<File> mountedPaths = new ArrayList<>();
try {
// Note: Despite restricting some access to /proc (http://stackoverflow.com/a/38728738/423105),
// Android 7.0 does *not* block access to /proc/mounts, according to our test on George's Alcatel A30 GSM.
bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));
// Iterate over each line of the mounts listing.
while ((lineRead = bufferedReader.readLine()) != null) {
Log.d(TAG, "\nMounts line: " + lineRead);
mountFields = lineRead.split(" ");
// columns: device, mountpoint, fs type, options... Example:
// /dev/block/vold/179:97 /storage/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
String device = mountFields[0], path = mountFields[1], fsType = mountFields[2];
// The device, path, and fs type must conform to expected patterns.
if (!(devicePattern.matcher(device).matches() &&
pathPattern.matcher(path).matches() &&
fsTypePattern.matcher(fsType).matches()) ||
// mtdblock is internal, I'm told.
device.contains("mtdblock") ||
// Check for disqualifying patterns in the path.
pathAntiPattern.matcher(path).matches()) {
// If this mounts line fails our tests, skip it.
continue;
}
// TODO maybe: check options to make sure it's mounted RW?
// The answer at http://stackoverflow.com/a/13648873/423105 does.
// But it hasn't seemed to be necessary so far in my testing.
// This line met the criteria so far, so add it to candidate list.
addPath(path, null, mountedPaths);
}
} catch (IOException ignored) {
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ignored) {
}
}
}
// Append the paths from mount table to candidate list, in reverse order.
if (!mountedPaths.isEmpty()) {
// See https://stackoverflow.com/a/5374346/423105 on why the following is necessary.
// Basically, .toArray() needs its parameter to know what type of array to return.
File[] mountedPathsArray = mountedPaths.toArray(new File[mountedPaths.size()]);
addAncestors(candidatePaths, mountedPathsArray);
}
// Add hard-coded known common paths to candidate list:
addStrings(candidatePaths, commonPaths);
// If the above doesn't work we could try the following other options, but in my experience they
// haven't added anything helpful yet.
// getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
// /storage/sdcard1/Android/data/com.mybackuparchives.Android/files
// so we want the great-great-grandparent folder.
// This may be non-removable.
Log.d(TAG, "Environment.getExternalStorageDirectory():");
addPath(null, ancestor(Environment.getExternalStorageDirectory()), candidatePaths);
// Context.getExternalFilesDirs() is only available from API level 19. You can use
// ContextCompat.getExternalFilesDirs() on earlier APIs, but it only returns one dir anyway.
Log.d(TAG, "context.getExternalFilesDir(null):");
addPath(null, ancestor(context.getExternalFilesDir(null)), candidatePaths);
// "Returns absolute paths to application-specific directories on all external storage
// devices where the application can place persistent files it owns."
// We might be able to use these to deduce a higher-level folder that isn't app-specific.
// Also, we apparently have to call getExternalFilesDir[s](), at least in KitKat+, in order to ensure that the
// "external files" directory exists and is available.
Log.d(TAG, "ContextCompat.getExternalFilesDirs(context, null):");
addAncestors(candidatePaths, ContextCompat.getExternalFilesDirs(context, null));
// Very similar results:
Log.d(TAG, "ContextCompat.getExternalCacheDirs(context):");
addAncestors(candidatePaths, ContextCompat.getExternalCacheDirs(context));
// TODO maybe: use getExternalStorageState(File path), with and without an argument, when
// available. With an argument is available since API level 21.
// This may not be necessary, since we also check whether a directory exists,
// which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.
// A "public" external storage directory. But in my experience it doesn't add anything helpful.
// Note that you can't pass null, or you'll get an NPE.
final File publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
// Take the parent, because we tend to get a path like /pathTo/sdCard/Music.
addPath(null, publicDirectory.getParentFile(), candidatePaths);
// EXTERNAL_STORAGE: may not be removable.
val = System.getenv("EXTERNAL_STORAGE");
if (!TextUtils.isEmpty(val)) addPath(val, null, candidatePaths);
if (candidatePaths.isEmpty()) {
Log.w(TAG, "No removable microSD card found.");
return null;
} else {
Log.i(TAG, "\nFound potential removable storage locations: " + candidatePaths);
}
// Accept or eliminate candidate paths if we can determine whether they're removable storage.
// In Lollipop and later, we can check isExternalStorageRemovable() status on each candidate.
if (Build.VERSION.SDK_INT >= 21) {
Iterator<File> itf = candidatePaths.iterator();
while (itf.hasNext()) {
File dir = itf.next();
// handle illegalArgumentException if the path is not a valid storage device.
try {
if (Environment.isExternalStorageRemovable(dir)
// && containsKnownFile(dir)
) {
Log.i(TAG, dir.getPath() + " is removable external storage");
return dir;
} else if (Environment.isExternalStorageEmulated(dir)) {
Log.d(TAG, "Removing emulated external storage dir " + dir);
itf.remove();
}
} catch (IllegalArgumentException e) {
Log.d(TAG, "isRemovable(" + dir.getPath() + "): not a valid storage device.", e);
}
}
}
// Continue trying to accept or eliminate candidate paths based on whether they're removable storage.
// On pre-Lollipop, we only have singular externalStorage. Check whether it's removable.
if (Build.VERSION.SDK_INT >= 9) {
File externalStorage = Environment.getExternalStorageDirectory();
Log.d(TAG, String.format(Locale.ROOT, "findSDCardPath: getExternalStorageDirectory = %s", externalStorage.getPath()));
if (Environment.isExternalStorageRemovable()) {
// Make sure this is a candidate.
// TODO: Does this contains() work? Should we be canonicalizing paths before comparing?
if (candidatePaths.contains(externalStorage)
// && containsKnownFile(externalStorage)
) {
Log.d(TAG, "Using externalStorage dir " + externalStorage);
return externalStorage;
}
} else if (Build.VERSION.SDK_INT >= 11 && Environment.isExternalStorageEmulated()) {
Log.d(TAG, "Removing emulated external storage dir " + externalStorage);
candidatePaths.remove(externalStorage);
}
}
// If any directory contains our special test file, consider that the microSD card.
if (KNOWNFILE != null) {
for (File dir : candidatePaths) {
Log.d(TAG, String.format(Locale.ROOT, "findSdCardPath: Looking for known file in candidate path, %s", dir));
if (containsKnownFile(dir)) return dir;
}
}
// If we don't find the known file, still try taking the first candidate.
if (!candidatePaths.isEmpty()) {
Log.d(TAG, "No definitive path to SD card; taking the first realistic candidate.");
return candidatePaths.iterator().next();
}
// If no reasonable path was found, give up.
return null;
}
/** Add each path to the collection. */
private static void addStrings(LinkedHashSet<File> candidatePaths, String[] newPaths) {
for (String path : newPaths) {
addPath(path, null, candidatePaths);
}
}
/** Add ancestor of each File to the collection. */
private static void addAncestors(LinkedHashSet<File> candidatePaths, File[] files) {
for (int i = files.length - 1; i >= 0; i--) {
addPath(null, ancestor(files[i]), candidatePaths);
}
}
/**
* Add a new candidate directory path to our list, if it's not obviously wrong.
* Supply path as either String or File object.
* @param strNew path of directory to add (or null)
* @param fileNew directory to add (or null)
*/
private static void addPath(String strNew, File fileNew, Collection<File> paths) {
// If one of the arguments is null, fill it in from the other.
if (strNew == null) {
if (fileNew == null) return;
strNew = fileNew.getPath();
} else if (fileNew == null) {
fileNew = new File(strNew);
}
if (!paths.contains(fileNew) &&
// Check for paths known not to be removable SD card.
// The antipattern check can be redundant, depending on where this is called from.
!pathAntiPattern.matcher(strNew).matches()) {
// Eliminate candidate if not a directory or not fully accessible.
if (fileNew.exists() && fileNew.isDirectory() && fileNew.canExecute()) {
Log.d(TAG, " Adding candidate path " + strNew);
paths.add(fileNew);
} else {
Log.d(TAG, String.format(Locale.ROOT, " Invalid path %s: exists: %b isDir: %b canExec: %b canRead: %b",
strNew, fileNew.exists(), fileNew.isDirectory(), fileNew.canExecute(), fileNew.canRead()));
}
}
}
private static final String Android_DIR = File.separator + "Android";
private static File ancestor(File dir) {
// getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
// /storage/sdcard1/Android/data/com.mybackuparchives.Android/files
// so we want the great-great-grandparent folder.
if (dir == null) {
return null;
} else {
String path = dir.getAbsolutePath();
int i = path.indexOf(Android_DIR);
if (i == -1) {
return dir;
} else {
return new File(path.substring(0, i));
}
}
}
/** Returns true iff dir contains the special test file.
* Assumes that dir exists and is a directory. (Is this a necessary assumption?) */
private static boolean containsKnownFile(File dir) {
if (KNOWNFILE == null) return false;
File knownFile = new File(dir, KNOWNFILE);
return knownFile.exists();
}
private static Pattern
/** Pattern that SD card device should match */
devicePattern = Pattern.compile("/dev/(block/.*vold.*|Fuse)|/mnt/.*"),
/** Pattern that SD card mount path should match */
pathPattern = Pattern.compile("/(mnt|storage|external_sd|extsd|_ExternalSD|Removable|.*MicroSD).*",
Pattern.CASE_INSENSITIVE),
/** Pattern that the mount path should not match.
* 'emulated' indicates an internal storage location, so skip it.
* 'asec' is an encrypted package file, decrypted and mounted as a directory. */
pathAntiPattern = Pattern.compile(".*(/secure|/asec|/emulated).*"),
/** These are expected fs types, including vfat. tmpfs is not OK.
* Fuse can be removable SD card (as on Moto E or Asus ZenPad), or can be internal (Huawei G610). */
fsTypePattern = Pattern.compile(".*(fat|msdos|ntfs|ext[34]|Fuse|sdcard|esdfs).*");
}
P.S.
<uses-permission Android:name="Android.permission.READ_EXTERNAL_STORAGE" />
を忘れないでください。また、APIレベル23以上では、必ずcheckSelfPermission
/requestPermissions
を使用してください。findSdCardPath(),
の値は、必要なときに毎回再計算するのではなく、キャッシュしたほうがよいでしょう。Log.d()
)があります。正しいパスが見つからない場合の診断に役立ちます。ログに記録したくない場合はコメントアウトしてください。