GooglePlayで公開している『マイゴチ』にクラッシュレポートが!!
アプリの機能でギャラリーから写真を読み込む場面があるのですが、どうやらその時に問題ありで強制終了してしまうとのこと。
送付されてきたスタックトレースを見てみると、ギャラリーで選択して戻ってきた時に実行される、選択写真のファイルパスを取得する処理でNullPointerExceptionが発生している模様。
発生している端末はXperia Z2+Android4.4。
初めXperiaってことは…機種依存かなとか考えてたんですが、ググってみてもそれらしい情報はないみたい…
手持ちのGalaxy2だと再現できないしどうしようと考えながら情報探してたらこんなのが。
Get real path from URI, Android KitKat new storage access framework
Android Gallery on KitKat returns different Uri for Intent.ACTION_GET_CONTENT
ビンゴ!!
Nexus10(Android4.4.4)を引っ張りだして試したら再現しました。
ひとまず手持ちの機材で確認出来るバグで安心しました。手持ちで再現しないとかになるとお手上げ状態なので。
原因
Android4.4以降は、Intent.ACTION_GET_CONTENTで戻ってくる写真のUriが、写真選択で使用するアプリにより異なる。
例えば、
public void startActivity() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/jpeg"); startActivityForResult(Intent.createChooser(intent, "Pick a source", 0); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) return; String[] columns = {MediaColumns.DATA}; Cursor cursor getContentResolver().query(data.getData(), columns, null, null, null); if (cursor.moveToFirst()) { File file = new File(cursor.getString(0)); // fileから写真を読み込む } }
選択した写真からそのファイルパスを取得する処理ですが、実行すると4行目のIntentで下記画面が表示されます。
赤枠内で選択した場合、11行目のdata.getData()で取得出来るのが
content://com.android.providers.media.documents/documents/image:234
赤枠以外のアプリで選択した場合、
content://media/external/images/media/234
上記のコードでは、赤枠以外の場合は問題なく動くが、赤枠内で選択した場合は11~14行目の処理でパスが取得できない。
Android4.4から対応のStorage Access Frameworkあたりを読めば詳しくわかるのかもしれないけど、ちょっと後回し。
修正方法
StackOverFrowを参考に修正。
Get real path from URI, Android KitKat new storage access framework
Android Gallery on KitKat returns different Uri for Intent.ACTION_GET_CONTENT
public void startActivity() { if (Build.VERSION.SDK_INT < 19) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/jpeg"); startActivityForResult(Intent.createChooser(intent, "Pick a source"), 0); } else { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/jpeg"); startActivityForResult(Intent.createChooser(intent, "Pick a source"), 1); } } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) return; if (requestCode == 0) { String[] columns = {MediaColumns.DATA}; Cursor cursor = getContentResolver().query(data.getData(), columns, null, null, null); if (cursor.moveToFirst()) { File file = new File(cursor.getString(0)); // fileから写真を読み込む } } else if (requestCode == 1) { String id = DocumentsContract.getDocumentId(data.getData()); String selection = "_id=?"; String[] selectionArgs = new String[]{id.split(":")[1]}; Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaColumns.DATA}, selection, selectionArgs, null); if (cursor.moveToFirst()) { File file = new File(cursor.getString(0)); // fileから写真を読み込む } cursor.close(); } }
とりあえず、修正した箇所は大きく2つ。
7-10行目:
Android4.4以上はIntent.ACTION_OPEN_DOCUMENTに変更。これで、写真選択の際に赤枠内のみ表示するようにしました。
26-31行目:
取得したUriからファイルパスを取得する処理を変更。
これでAndroid4.4でもギャラリーから写真を読み込むことが出来ました。
問題2:ダウンロードから画像を読み込む場合(2015/02/10追記)
上記のコードだと、ダウンロードフォルダから画像を読み込もうとした場合、アプリが落ちる事が判明しました(汗
※試してませんが、Googleドライブからの場合もNGでしょう。
ということで、さらに修正
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 省略 } else if (requestCode == 1) { Uri uri = data.getData(); if ("com.android.providers.media.documents".equals(uri.getAuthority())) { //ギャラリーからの場合 String id = DocumentsContract.getDocumentId(data.getData()); String selection = "_id=?"; String[] selectionArgs = new String[]{id.split(":")[1]}; Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaColumns.DATA}, selection, selectionArgs, null); if (cursor.moveToFirst()) { File file = new File(cursor.getString(0)); // fileから写真を読み込む } } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) { // ダウンロードからの場合 String id = DocumentsContract.getDocumentId(data.getData()); Uri docUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); Cursor cursor = getContentResolver().query(docUri, new String[]{MediaColumns.DATA}, null, null, null); if (cursor.moveToFirst()) { File file = new File(cursor.getString(0)); // fileから写真を読み込む } } else { //その他は・・・ //すいません。試してません。 } } }
※エラーハンドリングがないとか、重複が多いとかツッコミどころ満載ですが、大筋はこんな感じです
Googleドライブからの画像取得は、下記が参考になるかと思います。
http://stackoverflow.com/questions/25171246/open-a-google-drive-file-content-uri-after-using-kitkat-storage-access-framework
あとがき
今回はあまり深く追ってないので、とりあえずこうやったら直ったよというメモです。
ではでは~。
すみません、細かいことなんですが、
Cursor cursor = getContentResulver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaColumns.DATA}, selection, selectionArgs, null);
上記のコードは、
getContentResulver
ではなく
getContentResolver
ではないでしょうか?
もし気づいていたらすみません・・・。
ご指摘ありがとうございます。
誤記修正しました。
もう一点ありました。
String[] selectionArgs = new String[]{id.split(“:”)[1]};
上記のコードですが、
SO-03F Android4.4.2 ですと java.lang.ArrayIndexOutOfBoundsException が発生しました。
String id = DocumentsContract.getDocumentId(data.getData());
上記の結果が、「 acc=1;doc=1 」となっており、区切り文字が誤っているのかもしれません。
ご指摘ありがとうございます。
調べてみた結果、どこから画像を取得するかでonActivityResult()での処理を変える必要がありそうですね。
「画像」フォルダから取得する場合は、上記コードで問題ないようですが、「ダウンロード」や「Googleドライブ」からだと、それに適した処理にしないといけないっぽいです。
(ダウンロードフォルダの場合を追記しました)
さて、DocumentsContract.getDocumentId()で「 acc=1;doc=1 」となるのは、Googleドライブから取得した場合みたいですね。
<参考>
http://stackoverflow.com/questions/25171246/open-a-google-drive-file-content-uri-after-using-kitkat-storage-access-framework
Googleドライブからの画像取得はContentResolver.openInputStream(data.getData())でいけるかもです。
※すいません。Googleドライブを使用していないので試していませんが・・・
以上、参考になれば幸いです。