【Android開発】外部SDカードへの書込み処理を行うとSDカードが取り外しされる事象

自作アプリのJpopPVでは、動画を再生しているとお気に入り(以降キャッシュと表現)します。キャッシュ保存先は内部SDカードか外部SDカードか設定で変更出来ます。
キャッシュ保存先に外部SDカードを指定した際、何度か動画をキャッシュすると、なぜか外部SDカードが取り外しされる事象が発生し続けていました。
ネットで検索しても同じ事象の方がいなさそうで、原因究明に時間を要しました。
最新バージョンでは改善している事象ですが、改善させた内容を記載したいと思います。

外部SDカードが取り外しされる事象

冒頭でも挙げたように、以下の操作を行うと、ほぼ100%外部SDカードが取り外しされます。
1.JpopランキングもしくはPV検索にて動画再生
2.動画キャッシュが完了した時点で、オンライン再生からオフライン再生に切替
3.動画を切り替えて1~2を繰り返し

何度が動画を切り替えてキャッシュ処理を継続していると、アプリが例外で落ちます。
その後、ファイラーアプリ等で外部SDカードの内容を確認しようとしても、外部SDカードが取り外しされている為、アクセス自体不可。なぜかOS設定のストレージを確認しても、外部SDカードのマウント処理が出来ません。再度SDカードをマウントさせる場合、電源OFF→外部SDカード取り外し→取り付け→電源ONとう作業で再認識します。あまりにも不便ですね。

例外発生箇所

//バックグラウンド主処理
@Override
protected Integer doInBackground(String... argURL) {
    int intReadSize = 0;
    int intRetCode = MovieCacheAsyncTask.RESULT_CODE_OK;
	.
	.
	.
    byte[] buf = null;
    InputStream objRemoteInputStream = null;
    BufferedOutputStream objCacheOutputStream = null;

	.
	.
	.

    // キャッシュファイルの書き込みストリーム取得
    try {
		objCacheOutputStream = new BufferedOutputStream(new FileOutputStream(this.mCacheFile));
	} catch (FileNotFoundException e1) {
		e1.printStackTrace();
        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
        return intRetCode;
	}

    // キャッシュ書き込み開始
    buf = new byte[BUF_SIZE];
    intReadSize = 0;

    try {
		while ((intReadSize = objRemoteInputStream.read(buf)) != -1) {

			if(this.isCancelled() == false){
				// キャンセル処理がされていない場合、処理続行
		    	// リード終了では無いかつ、キャンセルされてなければ処理続行
		        if (objCacheOutputStream != null) {
		            try {
		            	// キャッシュへ書き込み
		            	objCacheOutputStream.write(buf, 0, intReadSize);
		            } catch (IOException e) {
		                try {
		                	objCacheOutputStream.close();
		                } catch (IOException ex) {
		        	        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
		                }
		                objCacheOutputStream = null;
		    	        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
		                break;
		            }
		        }

		        // 完了したキャッシュサイズ更新
		        this.mCachedBytes = this.mCachedBytes + intReadSize;

			}else{
				// キャンセル処理
		        try {
		        	// キャッシュ終了
		        	objCacheOutputStream.close();
		        	// キャッシュファイル削除
		            this.mCacheFile.delete();
		        } catch (IOException ex) {
		        }
		        objCacheOutputStream = null;

		        intRetCode = MovieCacheAsyncTask.RESULT_CODE_CANCEL;

		        break;
			}
		}
	} catch (IOException e) {
		// TODO 自動生成された catch ブロック
		e.printStackTrace();
        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
    } finally {

    	// インスタンス開放処理開始
        if (objRemoteInputStream != null) {
            try {
                objRemoteInputStream.close();
                objRemoteInputStream = null;
            } catch (IOException e) {
            }
        }
        if (objCacheOutputStream != null) {
            try {
            	objCacheOutputStream.close();
            	objCacheOutputStream = null;
            } catch (IOException e) {
            }
        }
        objConnection.disconnect();
        objConnection = null;
    }

    return intRetCode;
}

例外原因と改善方法

■例外原因
ファイルを書込みする際、BufferedOutputStreamのwrite()メソッドのみを使用している為、OSにはファイル出力予約のみ発行している状態。その状態から次のファイル書込み処理を行い続ける事により、多重書込みが永続的に発生。多重書込み処理が追いつかず外部SDカードが取り外しされる事象発生と思われます(確証はありませんが、、、)。

■改善方法
Yukiの枝折さんのサイトを参考にした所、事象の改善につながりました。
以下の手順にてファイル書込みを行うよう変更。
・FileOutputStreamのwrite()メソッド直後にflush()メソッドも呼び出し
・書込み最終処理にFileOutputStreamのgetFD()メソッドにてFileDescriptor取得
・FileDescriptorのsync()メソッドにてファイルとの同期実行

以下が改善したソースです

//バックグラウンド主処理
@Override
public Integer loadInBackground() {
	int intReadSize = 0;
	int intRetCode = MovieCacheAsyncTask.RESULT_CODE_OK;
	.
	.
	.
	byte[] buf = null;
	InputStream objRemoteInputStream = null;
	FileOutputStream objCacheOutputStream = null;

	.
	.
	.

    // キャッシュファイルの書き込みストリーム取得
    try {
    	objCacheOutputStream = new FileOutputStream(this.mCacheFile);
    } catch (FileNotFoundException e1) {
    	e1.printStackTrace();
    	intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
    	return intRetCode;
	}

	.
	.
	.

    try {
    	while ((intReadSize = objRemoteInputStream.read(buf)) != -1) {

			if(this.isCancelled() == false){
				// キャンセル処理がされていない場合、処理続行
		    	// リード終了では無いかつ、キャンセルされてなければ処理続行
		        if (objCacheOutputStream != null) {
		            try {
		            	// キャッシュへ書き込み
		            	objCacheOutputStream.write(buf, 0, intReadSize);
		            	objCacheOutputStream.flush();
		            } catch (IOException e) {
		                try {
		                	objCacheOutputStream.close();
		                } catch (IOException ex) {
		        	        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
		                }
		                objCacheOutputStream = null;
		    	        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
		                break;
		            }
		        }

		        // 完了したキャッシュサイズ更新
		        this.mCachedBytes = this.mCachedBytes + intReadSize;

		        // プログレス更新
		        this.onProgressUpdate();

			}else{
				// キャンセル処理
		        try {
		        	// キャッシュ終了
		        	objCacheOutputStream.close();
		        	// キャッシュファイル削除
		            this.mCacheFile.delete();
		        } catch (IOException ex) {
		        }
		        objCacheOutputStream = null;

		        intRetCode = MovieCacheAsyncTask.RESULT_CODE_CANCEL;

		        break;
			}
    	}
    } catch (IOException e) {
		e.printStackTrace();
        intRetCode = MovieCacheAsyncTask.RESULT_CODE_ERROR;
    } finally {

    	// インスタンス開放処理開始
        if (objRemoteInputStream != null) {
            try {
                objRemoteInputStream.close();
                objRemoteInputStream = null;
            } catch (IOException e) {
            }
        }
        if (objCacheOutputStream != null) {
            try {
            	objCacheOutputStream.flush();
            	objCacheOutputStream.getFD().sync();
            	objCacheOutputStream.close();
            	objCacheOutputStream = null;
            } catch (IOException e) {
            }
        }
        if(objConnection != null) {
 	        objConnection.disconnect();
        }
        objConnection = null;
    }

    return intRetCode;
}

最後に

外部SDカードへの書込みのみこの事象が発生します。内部SDカードへの書込みでは一切この事象は発生しません。なぜでしょうか、、、。
メインで使用しているF-01Fでは外部SDカードに64GBのMicroSDを使用している為、当初から外部SDカードに書込みたかったのですが、この事象に悩まされ続けていました。改善出来て良かったです。

この記事が参考となりましたら、JpopPVアプリのダウンロードとよろしければ以下の作業をして頂ければ幸いです。
・JpopPVアプリ評価で高評価
・JpopPVアプリ内の広告表示されている無料アプリのダウンロード(何度でも何回でもOKです)
今後も色々ブログ更新していきたいと思いますので、アプリ内でのこの動きはどう実装している?等ご質問頂ければブログ記事にしていきたいと思います。
よろしくお願いします。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*