一番かんたんなJava入門

これからJavaを始めようという人の為の超入門サイトです。丁寧、簡単にこだわった解説なので初心者にぴったりです。

【android】 SDカードのpathを取得する方法

time 2013/08/11

sponsored link

罠と化したgetExternalStorageDirectory()

 androidアプリを作っていて、SDカードにデータを保存する機能を実装したい時の話。
 androidは全て把握できないくらい様々な機種があり、残念なことにSDカードのマウント先(ディレクトリのpath)は、機種によって微妙な差があるらしいです。なので、プログラムの中で

File file = new File("mnt/sdcard", "data");

みたいにそのpathを絶対的に指定することはできません。なんとかしてその機種固有のpathを取得する必要があります。
 その為に用意されていたのが、

Environment.getExternalStorageDirectory()

 というクラスメソッドです。このメソッドを使えば、外部ストレージ(SDカード)のマウント先を簡単に取得することができていました。
 しかし、いつの頃からか、androidのとんでもない仕様変更により、この、名前からしていかにも外部ストレージのディレクトリをゲットしてくれそうな「Environment.getExternalStorageDirectory()」が、とんでもないトラップになってしまいました。それも結構悪質な。
 どんな悪質な罠か、結論からと申しますと、このメソッドで取得できるのは外部ストレージ(SDカード)のpathではなく、内部ストレージのpathなんです。
 androidの内部ストレージというのは、なぜかディレクトリ名が「../sdcard」となっています。外部がsdcardなのは分かるけど、なんで内部がsdcardなん?と思わずにいられないのですが、一体どういうことなんでしょう??
 しかも、外部ストレージのディレクトリを取得するはずの「Environment.getExternalStorageDirectory()」で、内部ストレージのpath「../sdcard」を取得してしまうんです。なので、もちろん「Environment.getExternalStorageDirectory()」で取得したディレクトリにファイルを作ってデータを保存しても、SDカードには一切何も書き込まれません。トラップ以外の何者でもないですよね・・。
 そこで、本物のSDカードのpathを取得するにはどうすればいいのかを、解説しまっす。
 解説なんて言うと偉そうですが、実は私自身このトラップにハマって困っていたところ、「外部ストレージのパスを取得する(Android 2.2~?) – 戌印-INUJIRUSHI- 」こちらのサイトの心優しい管理人様にいろいろ教えて頂いて(その節はありがとうございました)、解決することができたのでその方法を防備録として書いておきます。

システム設定ファイル(vold.fstab)をまさぐる

 androidには、システム設定ファイル(/system/etc/vold.fstab)というのがあります。そのファイルの中に、(その機種の)SDカードのマウント先が書いてあるようなので、そこからゲットします。
 具体的にシステム設定ファイルの中身がどうなっているか、ご自分のandroidの中の/system/etc/vold.fstabをテキストエディタで開いて見てもらえるとわかると思いますが、一応、僕の持っているHTC J oneの中にあるシステム設定ファイルの中身を載せておきます。

## Vold 2.0 fstab for cardhu

#######################
## Regular device mount
##
## Format: dev_mount 

こんな風になっています。この中の赤字の部分「/storage/ext_sd」が、取得するべきSDカードのマウント先(path)です。
 「dev_mount」で始まる行の半角スペース区切りで3番目のやつが、マウント先になるようです。ただし、見ての通り、sdcardのマウント先だけでなく、usbのマウント先?もあります。しかもこのシステム設定ファイルに載っている情報は機種によって違うので、他の機種はどんな情報が載っているのかよく分かりません。が、「dev_mount」(機種によっては「fuse_mount」)で始まる行の半角スペース区切りで3番目のやつがpathを示すというのはどうやら共通の仕様のようです。
 その中から、sdcardのpathだけを取得する為に、以下のメソッド(2つ)を定義します。2つ目のisMounted()は、1つ目のgetMount_sd()内で使っています。

// SDカードのマウント先をゲットするメソッド
@TargetApi(9)
private String getMount_sd() {
   List<String> mountList = new ArrayList<String>();
   String mount_sdcard = null;

   Scanner scanner = null;
   try {
      // システム設定ファイルにアクセス
      File vold_fstab = new File("/system/etc/vold.fstab");
      scanner = new Scanner(new FileInputStream(vold_fstab));
      // 一行ずつ読み込む
      while (scanner.hasNextLine()) {
         String line = scanner.nextLine();
         // dev_mountまたはfuse_mountで始まる行の
         if (line.startsWith("dev_mount") || line.startsWith("fuse_mount")) {	            	
            // 半角スペースではなくタブで区切られている機種もあるらしいので修正して
            // 半角スペース区切り3つめ(path)を取得
            String path = line.replaceAll("\t", " ").split(" ")[2];
            // 取得したpathを重複しないようにリストに登録
            if (!mountList.contains(path)){
               mountList.add(path);
            }
         }
      }
   } catch (FileNotFoundException e) {
      throw new RuntimeException(e);
   } finally {
      if (scanner != null) {
         scanner.close();
      }
   }

   // Environment.isExternalStorageRemovable()はGINGERBREAD以降しか使えない
   if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD){	    	
      // getExternalStorageDirectory()が罠であれば、そのpathをリストから除外
      if (!Environment.isExternalStorageRemovable()) {   // 注1
         mountList.remove(Environment.getExternalStorageDirectory().getPath());
      }
   }

   // マウントされていないpathは除外
   for (int i = 0; i < mountList.size(); i++) {
      if (!isMounted(mountList.get(i))){
         mountList.remove(i--);
      }
   }

   // 除外されずに残ったものがSDカードのマウント先
   if(mountList.size() > 0){
      mount_sdcard = mountList.get(0);
   }
	    
   // マウント先をreturn(全て除外された場合はnullをreturn)
   return mount_sdcard;
}

// 引数に渡したpathがマウントされているかどうかチェックするメソッド
public boolean isMounted(String path) {
   boolean isMounted = false;

   Scanner scanner = null;
   try {
      // マウントポイントを取得する
      File mounts = new File("/proc/mounts");   // 注2
      scanner = new Scanner(new FileInputStream(mounts));
      // マウントポイントに該当するパスがあるかチェックする
      while (scanner.hasNextLine()) {
         if (scanner.nextLine().contains(path)) {
            // 該当するパスがあればマウントされているってこと
            isMounted = true;
            break;
         }
      }
   } catch (FileNotFoundException e) {
      throw new RuntimeException(e);
   } finally {
      if (scanner != null) {
      scanner.close();
      }
   }

   // マウント状態をreturn
   return isMounted;
}

※コメントが多すぎて見づらかったらごめんなさい。
注1)isExternalStorageRemovable()は、getExternalStorageDirectory()で取得できるpathが、取り外し可能な外部ストレージ(つまりSDカード)ならばtrueを返し、内部ストレージ(上記の罠に嵌っている状態)ならば、falseを返します。
注2)/proc/mountsの中に現在マウント中のストレージ?のpathが書いてあります。やたらズラズラと書いてあるので正直、何を書いてあるのか理解不能でしたが、確かにSDカードをマウントしている状態だとそのpathが書いてあり、マウントしていない状態で見てみると書いていませんでした。

SDカードのpathを取得する

 上記の2つのメソッドを定義しておけば、

if(getMount_sd() != null){
   File file = new File(getMount_sd(), "data");

   // SDカードに保存する処理

}else{
   // SDカードのマウント先が見つからない場合の処理
}

 こんな風にして、SDカード内のファイルにアクセスできると思います。もし、SDカードが刺さっていなかったらgetMount_sd( )はnullを返すので、「SDカードが見つかりません」とかのメッセージをToastで出すようにしたらいいと思います。
 ただし、このシステム設定ファイルなんですが、GalaxyNexusや一部のカスタムROMには存在しないそうです。どないせー言うねん。。けど、 – 戌印-INUJIRUSHI- さん曰く、この方法が一番多くの機種に対して使えるらしいので、この方法でダメな機種は、僕は切り捨てることにしましたw
 androidのそういうとこ困る・・。

sponsored link

Androidアプリを作ろう

コメント

  • はじめまして、外部SDの読み込みについて調べていたらこのサイトを見つけました!
    かなり参考になりました!ありがとうございます!

    一つ。つたない追加ですが、自分の手元にあった「SC-01(GaraxyTab初代)」では、「/mnt/sdcard/external_sd」ではなく「/mnt/sdcard」が出てきました。
    原因はシステム設定ファイルに両方書き込まれていることが原因で、リストの最初を拾ったためこの結果になったようです(つまり3番目ではなく4番目に外部SDのパスがあるようです)なので、ディレクトリパスをStringに入れる前に、「/mnt/sdcard」のパスをリストから除外する処理を追加して回避しました。
    役に立つか判りませんが一応。
    今後も参考にさせて頂きます!

    多分失礼しました。

    by ミケ猫 €2013/10/25 12:04

  • ミケ猫さん、コメントありがとうございます。
    お役に立ててよかったです。
    そして貴重な情報ありがとうございます。
    実は私もいろいろ困ってまして・・。

    GaraxyTab初代では、システム設定ファイルに、

    『dev_mount sdcard /mnt/sdcard /mnt/sdcard/external_sd』

    って言う風に書いてるってことですかね?

    by nobuo €2013/10/25 12:25

  • […] 「スマホのSDカードパスを取得、機種依存対策」という情報によりますと、本当の外部にあるストレージ、SDカードのパスを取得する Android API は用意されていないと書いてあります。また、「SDカードのpathを取得する方法」には、SDカードを取得する方法をJavaで書いた複雑なコードが紹介されていますが、素人には簡単に利用することはできません。 […]

    by [15] SDカードのpath | 老輩のAndroid Studio奮闘記 €2016/04/25 19:35

  • […] http://nobuo-create.net/sdcard-2/ こういうサイトみても実は私には解決できなかった・・・ […]

    by fireタブレット内のClipboxのデータを外部ストレージのsdカードに保存する方法 | 自己投資図書館 €2016/05/07 23:29

down

コメントする



CAPTCHA


一番かんたんなJava入門

Androidアプリの作り方

忘備録

私の作ったAndroidアプリ

私の作ったWordPressテーマ

プロに教わるオンライン学習

管理人

Nobuo_CREATE

Nobuo_CREATE

WordPressテーマPrinciple、マテリアルを作ったり、Androidアプリを作ったり、Java入門サイトを作ったり、本を書いたりしています。どうぞよろしく。 [詳細]



sponsored link

オススメ書籍

[オススメpoint]

 この本は全く何も分からない初心者の方にお勧めです。プログラミングをするには覚えなければならない事が無茶苦茶いっぱいありますが、この本は教えてくれる順番、その構成が素晴らしいです。RPGのゲームを作るというストーリーにのっとってちょっとずつ難しいことを教えてもらえます。
 無機質で膨大なデータが載っているような本は読む気にならないという方は、こういうストーリー仕立ての本でチャレンジしてみてはいかがでしょうか?(注:RPGを作る為の本ではありません。)

[オススメpoint]

 ある程度、Javaを読み書きできるようになったら、オブジェクト指向について学ぶべきです。本書は、抽象的で分かったような分からんようなオブジェクト指向について、非常に分かりやすい例を出して説明してくれています。オブジェクト指向とは何なのか?という本質を掴むのにこれほど適した本はないと思います。オブジェクト志向の理念を理解できれば、より効率のいいコードをより楽に書けるようになるはずです。Java上級者を目指すなら必読の一冊!

只今、急拡大中

ゲームを作りたい人専門

個人フットサル予約サイト

ガチ専門

JavaからのRuby on Rails入門

JavaからのRuby on Rails入門